32 Bit Assembler

 

32 Bit Assembler, Intel IA-32 mimarisindeki 80386 ve üzeri işlemcilerin programlanması için kullanılan makine dilidir. Bugün kullanmakta olduğumuz Pentium 4, Celeron ve Pentium M (Centrino) teknolojili işlemciler, bu dil ile programlanmaktadır. 16 Bit Assembler ile geriye dönük olarak tam uyumlu olmasının yanında, korumalı kip ve 32 bitlik yazmaçların kullanımı ile işlemci gücünün tamamından faydalanılmasını sağlar.

 

Bu bölümün okunmasının evvelinde, aynı yazı dizisindeki 3. bölüm olan "Turbo Assembler ile Çalışmak" konusunun incelenmesinde fayda görüyorum. Zira şimdiki konunun içeriği, 3. bölümde işlenen tüm genel yapıları tekrarlamayacak; bunun yerine daha çok ek ve/veya gelişmiş özelliklerin irdelemesi yapılacaktır.

 

32 Bit Assembler Derleyicisi

 

32 Bit Assembler içerikli .asm dosyalarının derlenmesi ve .obj uzantılı nesne kodunun oluşturulma işlemini Microsoft Corporation tarafından sunulan "Microsoft Macro Assembler" (ml.exe) programı yapmaktadır. Oluşturulan nesne kodunun .exe uzantılı uygulamaya dönüştürülmesi ise, yine Microsoft Corporation tarafından sunulan "Microsoft Incremental Linker" (link.exe) tarafından yapılmaktadır. Her iki program da komut satırında çalışmakta ve dosyayı yardımcı parametrelerde belirtilen şekilde işlemektedir. Bu programlara tekil olarak ulaşmak pek zordur; dolayısıyla internette şu sitede MASM32 adlı kabinenin bulunması ve indirilmesi gerekir: http://www.movsd.com

 

Bu kabine içerisinde, 32 bit assembler programlama ile ilgili tüm yardım dosyaları, programlama araçları, include ve lib şeklinde yardımcı kütüphaneler ve örnekler mevcuttur. Kabine sürümü zaman içerisinde güncellenmektedir. Bu tarih itibariyle elimdeki en son sürüm 8.2'dir. Zip'li biçimde 3MB olan bu kabine, açıldığında disk üzerinde 20MB'tan daha fazla yer işgal etmektedir.

 

Bu yazı dizisinde, 32 bit assembler'in 32 Bit Windows ortamındaki kullanımı işlenecektir. Zira ait olunan ortam ile etkileşim kuralları, kodlama özgürlüğünü etkileyebilmektedir.

 

32 Bit Assembler'a Giriş

 

32 Bit Assembler'daki kodlama kurallarının altyapısı genel olarak 16 Bit Assembler'a dayanmaktadır. Ancak özellikle işlem verimliliği ve bellek erişimi hususunda 16 bitlik ortama göre büyük üstünlük ve kolaylık göstermektedir.

 

32 Bitlik İşlemcinin Yazmaçları

 

Adından da anlaşılacağı üzere, 32 bitlik işlemcideki temel yazmaçlar da 32 bit uzunluktadır. Bu yazmaçlar, amaç ve kullanımlarına göre aşağıdaki gibidir:

 

Genel amaçlı yazmaçlar: EAX(accumulator), EBX(base), ECX(counter), EDX(data)

İşaretçi yazmaçları: ESI(source index), EDI(destination index), EIP(instruction pointer)

Yığın yazmaçları: ESP(stack pointer), EBP(base pointer)

Durum yazmacı: EFLAGS

 

Genel amaçlı yazmaçlar, hemen hemen tüm komutlarda parametre (operand) olarak kullanılabilirler. IA-32 mimarisindeki 80386 işlemcisinden itibaren bu yazmaçlar da, bellek erişimi amacıyla köşeli parantez içerisinde kullanılabilmektedir. Yani eskiden sadece BX, SI, DI ve BP'nin özgürce kullanılabildiği bu amaca artık EAX, ECX ve EDX de hizmet etmektedir. Yine de uyumluluk ve kod anlaşılırlığı açısından kimi ortak kurallara haiz olmak yerinde bir fikir olabilir.

 

İşaretçi yazmaçları, isimlerini eski 16 bitlik yapıdaki görevlerinden almışlardır. Artık bu yazmaçların bellek gösterme görevi, yukarıda belirtildiği üzere genel amaçlı yazmaçların tümü tarafından gerçekleştirilebilmektedir. Ancak bir görev vardır ki sadece buradaki ESI ve EDI yazmaçlarına özgüdür: Dizi ve dize işlemleri. Bu tip işlemlerde ESI kaynak işaretçi, EDI ise hedef işaretçi olarak kullanılmaktadır.

 

Yığın yazmaçları, temel olarak işlevlere giriş ve çıkışlarda konum bilgisinin korunmasına yararlar. Bunlardan ESP yazmacı, hemen hemen sadece sistem tarafından kullanılır ve değiştirilir. Ancak aynı bölge üzerinde çalışan EBP yazmacı ise sadece kullanıcı/programcı tarafından değiştirilir. EBP'nin görevi, yığındaki bilgileri, daha özel olarak aktarılan parametreleri okumak veya yazmaktır.

 

Yukarıda adı geçen yazmaçlardan EIP ve EFLAGS dışında kalanlar, pek çok mantıksal ve aritmetik komut tarafından parametre olarak kabul edilmektedir. EIP, komut işaretçisi olduğu için sadece dallanma komutları(Jxx, CALL, RET ve IRET) ile değiştirilmesi mümkündür. EFLAGS yazmacının herhangi bir kullanıcı değerine atanması da dolaylı yollardan mümkün olmaktadır.

 

Bu 10 adet yazmaç, çoklu görev sisteminin temelini oluşturan, farklı görevlere ilişkin özgün bilgilerin temelini özgün yığın ile birlikte teşkil etmektedir. Yani çoklu görev anahtarlamaları esnasında yukarıdaki 10 yazmaç görev başına muhafaza edilir.

 

32 bitlik Intel işlemcilerinde sadece bu yazmaçlar değil, çeşitli görevlere yardımcı olmak amacıyla şu özel yazmaçlar da bulunur. İlk üç grup, programcı tarafından aktif olarak kullanılabilir. Sonraki 6 grup ise işletim sistemi tarafından kullanılır.

 

x87 FPU Yazmaçları: ST0, ST1, ST2, ST3, ST4, ST5, ST6, ST7 ve öteki denetim yazmaçları

MMX Yazmaçları: MM0, MM1, MM2, MM3, MM4, MM5, MM6, MM7

XMM Yazmaçları: XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, MXCSR

 

Segment Yazmaçları: CS, SS, DS, ES, FS, GS

Kontrol Yazmaçları: CR0, CR2, CR3 + CR4(Mimariye Pentium işlemcisi ile dahil olmuştur)

Sistem Tablosu İşaretçi Yazmaçları: GDTR, LDTR, IDTR, Görev Yazmacı

Hata Ayıklama Yazmaçları: DR0, DR1, DR2, DR3, DR6, DR7

Test Yazmaçları: TR4, TR5, TR6, TR7

MSR Yazmaçları

 

Burada adı geçen son 6 grup özel yazmaçlar, sisteme bağlı çalışan veya iş yapmaya yönelik uygulamalar yazan bir programcı tarafından değiştirilmemeli veya kullanılmamalıdır. Söz konusu yazmaçlar, işletim sistemleri tarafından kullanılmakta ve örneğin IDE(Integrated Development Environment)'ler tarafından talep edildiğinde, çalışması durdurulan program ile ilgili bilgi sağlamaktadır.

 

FLAT Bellek Modeli

 

16 bitlik işlemciler, günümüzdeki uygulamaları göz önüne aldığımızda bellek erişimi ve kullanımı bakımından son derece zayıf kalmaktadır. Zira 16 bitlik yazmaçlar ile 64KB'lık bellek bölgesine doğrudan yakın erişim sağlanabilmekte ve ancak segment yazmaçlarının değiştirilmesi ile maksimum 1MB'lık uzak bölge kontrol edilebilmekteydi. Oysa 32 bitlik işlemcinin yapısında varolan FLAT bellek modeli sayesinde 4GB'lık fiziksel bellek doğrusal olarak denetlenebilmektedir.

 

32 bit Windows ortamı için yazılan uygulamalar FLAT bellek modeline haiz olmalıdır. Bu bellek modeli kullanıldığında, program başlangıcından sonuna kadar segment yazmaçları aynı değeri göstermektedir. Bu yapısıyla bir COM programını andırmaktadır. Gerçekten de FLAT bellek modelinde yapılan erişimler NEAR (yakın) bellek adreslemesi dahilindedir(bkz. Bölüm - 4: COM ve EXE Dosya Biçimleri). Bellek erişiminde herhangi bir hesaplamanın yapılmaması, işlem kolaylığını ve dolayısıyla hızı arttırmaktadır. Ayrıca bu modelde, aynı olan bu segment yazmaç değerlerinin hiçbir önemi yoktur; zira sistemde varolan fiziksel belleğin ilk 4GB'lık bölümü doğrusal olarak adreslenmektedir. Yani, herhangi bir CS değeri için IP'nin göstermiş olduğu değerin bellekteki yeri sabittir. Bu durum, kimi zaman mutlak erişimlerin yapılmasına gerek duyduran "görecelilik" yapısını ortadan kaldırmakta; dolayısıyla tüm adreslemeler mutlak olabilmektedir. Yani fiziksel bellekteki 5. hücreye herhangi bir değer atamak için bu modelde adres yerine 0x00000005 yazmak yeterlidir. Oysa eski 80286 modelinde FAR (uzak) bellek adreslemesi kullanılarak bunun 0x0000:0x0005 olarak belirtilmesi gerekmektedir. Ya da daha uzun ve zahmetli bir yolla önce DS'ye 0x0000 verilmeli, daha sonra da SI'nin 0x0005 değerini göstermesi sağlanmalıdır.

 

FLAT bellek modelinin kullanımı, ESI ve EDI gibi işaretçi yazmaçlarının çalıştığı segmentlerin kimi durumlara özgü olarak farklı belirtilmesini de gereksiz kılar. Zira normal bellek gösterimlerinde, SEGMENTED (parçalı) bellek modelinde ESI işaretçisi DS üzerinde, EDI işaretçisi ise ES üzerinde çalışır. Kimi durumlarda varsayılan segmentlerin farklı belirtilmesi gerekebilir. Örneğin MOV [EDI], ES:[ESI]. Ancak program dahilindeki tüm segment yazmaçlarının aynı değeri göstermesinden ötürü bu farklı belirtmenin bir anlamı olmayacaktır ki buna ihtiyaç da olmayacaktır.

 

Korumalı Kip (Protected Mode)

 

İlk DOS zamanlarında, sistemde varolan belleği denetleyen ve izinsiz erişimleri durdurabilen herhangi bir mekanizma yoktu. Dolayısıyla yanlış veya acemice yazılmış bir program, normalde kendisine ait olabilecek 64KB'lık belleğin dışındaki bölgelere kontrolsüz erişimde bulunabilmekte ve istenmeyen sonuçlara neden olabilmekteydi(bkz. Bölüm 4: COM ve EXE Dosya Biçimleri). Zira 0xA000 segmentinde bulunan VGA bellek bölgesi, programların görsel öğeleri görüntülemek için kullandığı paylaşımlı bir alandır. Bu bölgedeki veriler, görüntü arabirimi tarafından sürekli okunur ve ekrana piksel olarak yansıtılır. Bu alana, belirli bir döngü dahilinde veri yazmakta olan bir kod, döngü yapısındaki bir aksaklık nedeniyle hedefini aşabilir ve yine paylaşımlı olarak sunulan DOS işletim sistemi kodunu ve hatta bellekte kayıtlı olan BIOS ayarlarını değiştirebilir. Bu durumda sistem, herhangi bir açıklama dahi yapamadan kilitlenebilir ve yeniden başlatılması gerekebilir. Kaydedilmemiş herhangi bilgiler de kaybedilir.

 

Korumalı Kip, programa tahsis edilmiş bölgelerin dışına erişilmesi durumunda buna izin vermeyen ve gerekirse sistemin bütünlüğünün ve güvenliğinin korunması adına programı sonlandırabilen bir mekanizmadır. Buna benzer bir yapı, 16 bit Windows (Windows 3.1) zamanında da mevcuttu. Ancak eski Windows'ların "Benzetimli Çoklu Görev (Simulated Multitasking)" sağlamasından ötürü kimi zaman, sistem hatayı oluşturan programa müdahalede bulunana kadar program, farklı bellek bölgelerini değiştirebiliyordu. Dolayısıyla çoklu görev ortamı ve güvenliği tam olarak sağlanabilmiş değildi. Bu durum, fazla miktarda bellek kullanımına ihtiyaç duyan mühendislik veya simülasyon uygulamalarının, öteki programlarla paralel çalıştırılabilmesini tehlikeye sokuyordu. Hatalı bir programın, işletim sistemine ait bölgelere müdahalede bulunması sonucunda herhangi izinsiz denetim gerçekleştirmeyen, ancak tamamen masumane biçimde, evvelden hatalı programın değiştirmiş olduğu bölgede yer alan bir işletim sistemi fonksiyonunu çağıran bir program, sistemin "Siyah Ekran" ile kilitlenmesine yol açabiliyordu. Dikkat edilirse, Windows sisteminde oluşan hatalarda kullanıcıyı bilgilendirmek amacıyla "Mavi Ekran" çıkar ve hatanın kaynağını, kullanılan çözüm yöntemini belirtir. "Siyah Ekran", sistemin tamamen aciz kaldığının bir göstergesidir.

 

Bugünkü Windows işletim sistemlerinde varolan anlamdaki Korumalı Kip, donanım destekli olarak sağlanmakta ve öncekine göre mukayese edilemez bir üstünlük sağlamaktadır. Görevlere tahsis edilmiş bellek bölgelerinin kullanım amaçları da belirtilmekte, dolayısıyla Sayfa Hatası (Page Fault) olaylarının niteliği de belirtilebilmektedir. Zira bir program, sistemden 16KB'lık bir bölge talep eder ve 20K'lık bir bölgeyi okumaya veya yazmaya çalışırsa burada, yapılmaya çalışılan işin niteliğine göre hata mesajı verilir. Oluşabilecek hata, eğer program dahilinde hesaba katılmış ve ele alınabiliyorsa (Error Handling, Error Trapping, Trappable Error, Exception Handling), yapılan hataya işletim sistemi tarafından müdahalede bulunulmaz. Ancak böyle bir durum söz konusu değilse, işletim sisteminin tek bir müdahalesi vardır: Uygulamayı sonlandırmak. Sonlandırma işlemi esnasında, programın daha önceden işletim sisteminden talep ettiği tüm bellek bölgeleri de yeni kullanımlara açılır. Bu sayede bellek sızıntısı veya kaybı önlenir. Eski sistemlerde, kimi zaman programların sonlandırılmasındaki bellek kayıpları nedeniyle, herhangi bir uygulama tarafından kullanılmadığı veya rezerve edilmediği halde sistemdeki bellek çok düşük miktara inebiliyor; başka bir tabirle sistem kaynakları tükenebiliyordu.

 

32 Bit Assembler'da Kodlamaya Giriş

 

32 Bit Assembler'deki kodlama kuralları, genellikle 80286 işlemcisine hitap eden 16 Bit Assembler'dekine benzer. Ancak buradaki makro üstünlükleri, kodun anlaşılabilirliğine katkıda bulunur. Makro konusuna daha ileriki başlıklarda değinilecektir. Şimdilik sadece genel kodlama kuralları verilecektir.

 

Komut Parametreleri

 

32 bit assembler'da komutlar, birlikte kullanıldıkları parametre sayısına göre 0, 1 ve 2 olmak üzere 3 grupta incelenebilir. Parametrelerin arasında virgül bulunmalıdır. Komutların ve yazmaç adlarının yazımında büyük/küçük harf kuralı önem taşımamaktadır. Ancak değişken adları, 16 bit assembler'daki kurallara haizdir. Aşağıda bunlara ilişkin örnekler yer almaktadır.

 

mov eax, ecx           ; Komut: MOV, ECX yazmacının içeriği, EAX yazmacına kopyalanır.

AND ESI, sayac        ; Komut: AND, ESI ile "sayac" adlı değişkenin içerikleri bit düzeyinde AND işlemine tabi tutulur; sonuç yine ESI yazmacına yazılır.

 

Yukarıdaki örneklerde olduğu gibi, 2 parametre alan komutlarda 1. parametreye hedef, 2. parametreye ise kaynak denir. Yapılan işlemin sonucu her zaman hedefe yazılır.

 

mul dword ptr [ESI]     ; Komut: MUL, EAX yazmacının içeriği ile ESI ile gösterilen adresteki 4 baytlık veri çarpılır; sonuç EDX:EAX çiftinde bulunur.

NEG ebx                   ; Komut: NEG, EBX yazmacının içeriği, 2'ye tümleyen aritmetiğine göre ters işaretli yapılır.

 

CBW                ; Komut: CBW, AL yazmacının içeriği, işareti AH'a aktarılacak şekilde AX'e yazılır.

pushad            ; Komut: PUSHAD, Tüm 32 bitlik genel amaçlı yazmaçlar yığına kopyalanır.

 

Komutların kullanımında, parametrelerin uyumluluğu çok önemlidir. Genellikle her iki parametrenin boyutunun da aynı olması gerekir. Örneğin:

 

mov eax, edx

 

Bu işlemde 32 bitlik EDX yazmacının içeriği, 32 bitlik EAX yazmacına kopyalanmaktadır. Ancak aşağıdaki kodlama yanlıştır ve assembler tarafından hata olarak nitelenir:

 

mov eax, dx

 

Burada, programcının asıl niyetinin ne olduğu assembler'i ilgilendirmez. Parametrelerin (operandların) uyumsuz olduğu bir hata mesajı ile belirtilir.

 

Komutların yazımındaki MOV, AND, MUL gibi ibareler, aslında donanımda ikili sayılarla belirtilen işlemlerin daha anlaşılır olması amacıyla kullanılmaktadır. Sadece Intel'in değil, tüm öteki mikroişlemci ve mikrodenetleyici üreticilerinin sunmuş oldukları assembler'lar da bu şekilde MNEMONIC adı verilen kelimeleri çözümlerler. Bu komutlardan sonra gelen parametrelere göre ikili sayı dizeleri oluşturulur. Gerçekte 32 bitlik EAX yazmacına 0x12345678 değerinin yüklenmesi için kullanılan işlem kodu şöyledir:

 

MOV EAX, 0x12345678    =>    A1 78 56 34 12

 

Bu sistemin kullanımı, herhangi bir işlem belirtmede hassasiyet kaybına yol açmaz. Tüm modern assembler'larda bu teknik kullanılır.

 

Yazmaç Yapılarında Geriye Dönük Uyumluluk

 

32 Bitlik İşlemcinin Yazmaçları başlığı altında belirtilen tüm genel amaçlı yazmaçların ilk 16 bitlik kısımları, eskiden kullanılan ve başlarında (E) harfi bulunmayan adlarla anılabilir. Örneğin 32 bitlik EAX yazmacının ilk 16 bitlik kısmı AX adını alır ve işlemlerde bu şekilde kullanılır. Aşağıdaki kod örneklerinde, 32 bitlik yazmaçların her bir baytına erişim yöntemi verilmektedir.

 

EAX: 32 Bit

16 Bit

AX: 16 Bit

8 Bit

8 Bit

AH: 8 Bit

AL: 8 Bit

 

8 bitlik (1 baytlık) BYTE değişkenine EAX'in ilk 8 bitlik bölümünü aktarmak için:

 

MOV BYTE, AL

 

16 bitlik(2 baytlık) WORD değişkenine EAX'in ilk 16 bitlik bölümünü, daha bilimsel tabirle düşük kelimesini (LOW WORD) aktarmak için:

 

MOV WORD, AX

 

16 bitlik WORD değişkenine EAX'in yüksek kelimesini (HIGH WORD) aktarmak için doğrudan bir komut yoktur. Programlama tekniklerinin kullanılması gerekir:

 

ROL EAX, 16            ; 32 bitlik EAX yazmacı, sola doğru 16 bit döndürülür. Dolayısıyla kelimeler yer değiştirmiş olur.

MOV WORD, AX        ; Bu yer değişiminden sonra eskiden yüksek olan kelime düşün olmuştur ve WORD değişkenine doğrudan aktarılabilir.

 

Yukarıda belirtilen işlem yapıldıktan sonra EAX yazmacının eski haline getirilmesi için bir kere daha 16 bitlik döndürme işlemine tabi tutulması gerekir. Döndürmedeki miktar, yazmaç boyutunun yarısı olduğundan yönün herhangi bir önemi yoktur.

 

Daha önceki başlık altında belirtilmiş olan "Parametrelerin Uyumluluğu" konusunda verilen örneğe geri dönmek gerekirse:

 

MOV EAX, DX

 

işlemindeki amacın DX'in, EAX yazmacına aktarılması olduğunu düşünelim. Bu işlemin kurallar dahilinde yapılabilmesi için şu olanaklar mevcuttur:

 

MOVZX EAX, DX        ; DX yazmacının içeriğini, boyutu aşan bölümünü 0 yapmak suretiyle EAX yazmacına kopyalar.

 

MOVSX EAX, DX        ; DX yazmacının içeriğini işaretli bir sayı kabul ederek, işaret bitini EAX'in yüksek kelimesindeki tüm bitlere ve kendisini de düşük kelimesine kopyalar. Bu şekilde 2'ye tümleyen sisteminde sayının büyüklüğü ve işareti korunur.

 

Şu iki yöntem de bu işe yaramakta, ancak daha zahmetli olmaktadır:

 

XOR EAX, EAX        ; EAX yazmacının içeriğini 0 yapar.

MOV AX, DX            ; DX yazmacının içeriğini AX yazmacına kopyalar. Dolayısıyla EAX = AX olduğundan amaca ulaşılmış olur. Ancak sayı işaretsiz kabul edilmiştir.

 

MOV AX, DX            ; DX yazmacının içeriğini AX yazmacına kopyalar.

CWDE                    ; AX yazmacının içeriğini işaretli bir sayı kabul ederek, işaret bitini EAX'in yüksek kelimesindeki tüm bitlere(16-31) kopyalar.

 

Buradaki örnekler, veri taşıma ile ilgili ekstralar niteliğinde olup, tüm işlemler için bu farklı yollar mümkün olmamaktadır.

 

Yazmaçların Değerlerinin Korunumu

 

Daha önce de belirtildiği gibi, uygulama kodunun yazımı esnasında malik ortamın kurallarına uymak gerekir. 32 Bit Assembler ile Windows ortamında çalışacak kod yazarken çoğunlukla Windows API(Application Programming Interface)'den faydalanılır. Dolayısıyla çağırılan fonksiyonlar ile birlikte x86 işlemcisinin sınırlı sayıdaki kaynağını, buradaki konu olarak yazmaçlarını güvenli şekilde paylaşmak gerekir.

 

32 bitlik işlemcide, programcının serbestçe değiştirebildiği 8 yazmacın varlığından söz edilmişti: EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP. Bu yazmaçlardan ESP ve EBP, çok özel amaçlar için kullanılmaktadır. Buradaki özel amaç, işlevlere giriş ve çıkış konumlarını muhafaza etmek, parametrelere ve yerel değişkenlere erişmektir. Dolayısıyla bu yazmaçların alt işlevler tarafından değiştirilmesi, üst kod tarafından yanlış kullanılmalarına yol açar. Bu iki yazmacın, işlev giriş ve çıkışında bulunduğu gibi bırakılması gerekir. Eğer işlev dahilinde mutlaka kullanılmaları gerekiyorsa eski değerlerinin muhafaza edilmesi ve kullanım bittiğinde yüklenmesi gerekir. Geriye serbest kullanım için 6 yazmaç kalır. Bu yazmaçlardan EBX, ESI ve EDI, eski özelliklerindeki bellek erişimi ve adresleme üstünlüklerinden ötürü dizi işlemlerinde sıklıkla kullanılabilmektedir. Dolayısıyla üst kod, alt kodu çağırırken buradaki değerlerinden faydalanabilir ve alt kod tarafından bu yazmaçların değiştirilmesi, yanlış adreslemelere yol açar. Geriye sadece şu 3 yazmaç kalır: EAX, ECX ve EDX. Bunu bir kurala dönüştürmek gerekirse, Windows API kullanılırken herhangi bir işlev çağırıldığında, dönüşte EAX, ECX ve EDX yazmaçlarının eski değerlerinde olması beklenmemelidir. Dolayısıyla kritik değer taşımaları durumunda bu yazmaçların yığında saklanmaları ve ardından işlevlerin çağırılması gerekir.

 

Herhangi bir işlevin, yaptığı işlemin gereksinimlerinden ötürü ESI, EDI ve EBX yazmaçlarını kullanması gerektiğini varsayalım. Bu durumda aşağıdaki gibi bir kod eklentisi kullanılmalıdır:

 

Function1    proc

 

push esi

push edi

push ebx

 

; Buraya işlev kodu yazılır.

 

pop ebx

pop edi

pop esi

 

Function1    endp

 

ESP ve EBP yazmaçları aracılığıyla yerel değişkenlere ve işlev parametrelerine erişim, bu yazı dizisinin 3. bölümünde belirtildiği gibidir. Ancak 32 bit assembler'de yerel değişken ve parametrelere erişim için bu yazmaçların programcı tarafından değiştirilmesine gerek bırakmayacak yapılar mevcuttur. Assembler, gerekli adres hesaplamalarının tamamını kendi yapmakta ve kullanıcıyı daha çok işin yapımına yöneltmektedir. Bu konu ile ilgili ayrıntılara ileriki başlıklarda girilecektir.

 

Veri Türleri

 

32 bit assembler'da kod yazarken, hangi komutun hangi tür veriyi işleyeceğini ve bu verilerin boyutlarının ne olması gerektiğini iyi bilmek gerekir. Aksi takdirde çalışır görünen bir kod, programcının istediği amaca hizmet etmeyebilir.

 

1. Komut Parametre Veri Türleri (Instruction Operands)

 

Assembler'da, komutlar ile birlikte kullanılmak üzere 3 tip parametrik veri türü mevcuttur:

 

Mutlak Veri (Immediate)

 

Mutlak veriler, tamamen rastgele nitelikli ve programcı tarafından tanımlanan sayısal büyüklükler olup 8, 16 veya 32 bit boyutlarında olabilmektedir.

 

mov eax, "c"               ; "c" harfinin ASCII karşılığı, EAX yazmacına yüklenir.

mov eax, "ATIL"            ; "ATIL" kelimesindeki harflerin ASCII karşılıkları EAX yazmacına yüklenir. Kelimenin 4 harfi geçmesi hataya yol açar, çünkü EAX yazmacının boyutu 4 bayttır.

mov edx, 175                ; 175 sayısı EDX yazmacına yüklenir.

mov ecx, 3692af2dH    ; 3692af2dH sayısı, ECX yazmacına yüklenir.

 

Yukarıdaki örneklerde 32 bitlik yazmaçlar kullanılmış olmasına karşın, 16 bitlik veya 8 bitlik yazmaçlar için de notasyon aynı şekildedir. Mutlak veri, komut ile birlikte gelmesinden ötürü işlemci arabelleğinde doğrudan varolduğundan, yazmaçlara aktarım hızı en yüksek seviyededir.

 

Bellek Verisi (Memory)

 

Bellek verisi, sistemde varolan fiziksel veya mantıksal bellek üzerindeki hücrelerde varolan verilerdir. İşlemci, belleğe erişim sağlamak suretiyle bu verileri komutların yanında parametre olarak kullanabilir.

 

mov eax, [edi]                   ; EDI yazmacının göstermiş olduğu bellekten itibaren 32 bitlik veri EAX yazmacına aktarılır.

mov ax, word ptr [edi]        ; EDI yazmacının göstermiş olduğu bellekten itibaren 16 bitlik veri AX yazmacına aktarılır. Burada boyut ayarlaması kullanılmıştır. Boyut ayarlaması, özellikle tek parametre alan işlemlerde kullanılır. Buradaki kullanımın sağladığı bir düzeltme yoktur; zira assembler zaten 16 bitlik olduğunu varsayacaktır.

mov [mutlakadres], al        ; Mutlak adres ile belirtilen adresteki 1 baytlık hücreye AL yazmacının içeriği aktarılmıştır.

 

Ne 16 bitlik işlemcilerde, ne de 32 bitlik işlemcilerde doğrudan doğruya bellekten belleğe işlem yapan bir komut yoktur. Ancak şu iki özel durum, 32 bitlik işlemci ile gelen yenilikler arasındadır:

 

mov dword ptr [adres], mutlakdeğer        ; Bu işlem ile herhangi bir bellek gözüne bir mutlak değer doğrudan doğruya yazılabilir.

push dword ptr [adres]                            ; Bu işlem ile herhangi bir bellek gözündeki değer, doğrudan doğruya yığına atılabilir.

 

Bir bellek gözündeki değeri, başka bir bellek gözüne kopyalamanın iki yolu vardır:

 

mov eax, [adres1]        ; 1 çevrim

mov [adres2], eax        ; 1 çevrim

 

Bu işlemde EAX dışında herhangi bir genel amaçlı yazmaç da kullanılabilir, ancak yazmaç içeriğinin değişeceği unutulmamalıdır. Aşağıdaki işlem ise herhangi bir yazmaç kullanımını gerektirmez, ancak yukarıdakine göre nispeten daha yavaştır. Dolayısıyla döngüler içerisinde kullanımı hız ve performans kaybına yol açar.

 

push [adres1]            ; 4 çevrim

pop [adres2]              ; 6 çevrim

 

Her iki yöntemin kombinasyonu ele alınarak başarımı en yüksek bellek kopyalama işlemi gerçekleştirilebilir. Önce EAX yığına atılır; daha sonra ise ilk yöntemde olduğu gibi bellek verileri aktarılır. Son olarak da EAX yığından geri alınır. Bu işlem toplam 7 çevrim sürecektir. Döngüler içinde kullanılması durumunda ise sadece 1 kereliğine EAX'in yığına atılması ve sonra işlemler bittiğinde geri alınması yeterli olacak; bu da başarımı mümkün olan en yüksek düzeye çıkaracaktır.

 

Yazmaç Verisi (Register)

 

mov eax, ecx                ; ECX yazmacının içeriği, EAX yazmacına kopyalanır.

mov esi, edi                    ; EDI yazmacının içeriği, ESI yazmacına kopyalanır.

mov ds, ax                    ; AX yazmacının içeriği, DS segment yazmacına kopyalanır.

mov dl, bl                      ; BL yazmacının içeriği, DL yazmacına kopyalanır.

 

Yazmaçlar üzerinde işlem yapmanın, boyut uyumluluğu dışında özel kuralları yoktur. Ancak segment işaretçilerinin (CS, SS, DS, ES, FS, GS) genel amaçlı yazmaç niteliğinde olmadığı ve her komut dahilinde kullanılamayacağı unutulmamalıdır.

 

2. Temel Veri Türleri (Fundamental Data Types)

 

Komutların işleme tabi tutacağı verilerin büyüklüklerine göre de bir sınıflandırma mevcuttur. Aşağıda temel veri türlerinin büyüklük sınıflandırmasına ilişkin tablo görülmektedir.

 

Veri

Boyut

Alternatif İfade Biçimi

Nibble

4 Bit

-

Byte

8 Bit

High Nibble

Low Nibble

Word

16 Bit

High Byte

Low Byte

Doubleword

32 Bit

High Word

Low Word

Quadword

64 Bit

High Doubleword

Low Doubleword

Double Quadword

128 Bit

High Quadword

Low Quadword

 

Bu veri türlerinden Quadword, IA-32 mimarisine Intel486 işlemcisi ile; Double Quadword ise Pentium III işlemcisindeki SSE(Streaming SIMD Extensions) komutları ile girmiştir. Bu veri türlerine ilişkin boyutların nasıl belirtileceğine ilişkin konuya ileriki başlıklarda değinilecektir.

 

Bu veriler, genellikle bellek üzerinde ele alınır ve komutlar tarafından işleme tabi tutulabilmeleri için işlemcinin ilgili bellek bölgesine erişmesi gerekir. Bu noktada "Doğal Sınırlar (Natural Boundaries)" kavramı ortaya çıkar. Doğal sınır, bir veri türünün boyutuna göre bellek üzerinde bulunmasının daha verimli olduğu yerlerdir. Bu yerler, veri türünün büyüklüğü ile tam bölünebilen noktalardır. Örnek olarak Word veri türüne ilişkin doğal sınırlar, 2'ye bölünebilen adreslerdir. Dolayısıyla adres değerinin ikili ifadesindeki en düşük bit her zaman sıfır (0) olmalıdır. Aynı şekilde Doubleword veri türü için 4, Quadword veri türü için 8 ve Double Quadword veri türü için 16 baytlık doğal sınırlar tanımlıdır.

 

IA-32 mimarisindeki işlemcilerin normal komut seti, söz konusu veri türlerinin doğal sınırlar dahilinde bulunmalarını gerektirmez. Ancak doğal sınırlarda bulunmayan bir veriye erişim için işlemcinin art arda 2 bellek erişimi yapması gerekir. Bu da işlem başarımını düşürür. Özellikle yığın konusunda bu ayrıntı büyük önem taşır. Dikkat edildiyse PUSH ve POP işlemleri ile yığına atılabilecek en küçük veri boyutu 16 bittir. Dolayısıyla herhangi bir ekstra müdahalede bulunulmaksızın yığın işaretçisinin 16 bitlik doğal sınırlarda erişim yapacağı açıktır. Ancak 32 bit assembler'da, normalde 4 baytlık doğal sınırlarda bulunan yığına 2 baytlık bir veri atılmasının ardından, yeni bir hizalama yapılmaksızın sürekli olarak 4 baytlık verilerin yüklenmesi ve boşaltılması ile çalışıldığında işlem başarımı %50'ye kadar düşebilecektir. Dolayısıyla programcının bu ayrıntıyı da göz önünde bulundurarak ekstra hizalama yapması gerekir.

 

IA-32 mimarisine Pentium III işlemcisi ile gelen SSE alt komut setinde, Double Quadword veri türü üzerinde işlem yapan kimi komutlar ise, doğal sınırlar dahilinde bulunmayan veriler üzerinde işlem yapılmaya çalışıldığında genel koruma hatası vermektedir. Dolayısıyla bu komutların işleyeceği verilerin adres ifadelerindeki en düşük 16'lı rakam sıfır (0) olmalıdır.

 

Not: Yüksek seviyeli programlama dillerine ilişkin derleyicilerin ayarlarında, yapı üyelerinin hizalamalarına ilişkin seçenekler bulunur. Örneğin 16 bitlik DOS platformu için programların yazıldığı Turbo C++'ta Options->Compiler->Code Generation menüleri vasıtasıyla erişilen ayarlarda "Word Alignment" seçeneği, yapı üyelerinin her halükarda 16 bitlik doğal sınırlarda hizalanmasını sağlar. Benzer şekilde 32 bitlik Windows platformu için programların yazıldığı Microsoft Visual C++'ta ise Project->Settings->C/C++->Code Generation->Struct Member Alignment yoluyla erişilen ayarlarda 1, 2, 4, 8 ve 16 bayt seçenekleri bulunur. Ancak bu hizalamaların bir çeşit israfa yol açarak bellek kullanımını arttıracağı unutulmamalıdır.

 

3. Sayısal Veri Türleri (Numeric Data Types)

 

Yukarıda belirtilmiş olan bellek veri türleri, çeşitli komutlar tarafından farklı nitelikli sayısal veri türleri olarak kabul edilir ve işleme tabi tutulur. Aşağıda, bu veri türlerinin yorumlanışına ilişkin tablo görülmektedir.

 

Sayısal Tür

Boyut

C++ Karşılığı

Tanım Aralığı

İşaretli Tamsayı

8 Bit

char

-128...127

İşaretli Tamsayı

16 Bit

short

-32768...32767

İşaretli Tamsayı

32 Bit

int

-231...231-1

İşaretli Tamsayı

64 Bit

__int64*

-263...263-1

İşaretsiz Tamsayı

8 Bit

unsigned char

0...255

İşaretsiz Tamsayı

16 Bit

unsigned short

0...65535

İşaretsiz Tamsayı

32 Bit

unsigned int

0...232-1

İşaretsiz Tamsayı

64 Bit

unsigned __int64*

0...264-1

Ondalık sayı

32 Bit

float

1.18 x 10-38...3.40 x 1038

Ondalık sayı

64 Bit

double

2.23 x 10-308...1.79 x 10308

Ondalık sayı

80 Bit

long double

3.37 x 10-4932...1.18 x 104932

 

*Microsoft C/C++ Compiler tarafından tanımlı olan bu veri türü, farklı derleyiciler tarafından tanınmayabilir. MSDN'de bu tür için "Microsoft Specific -->" ibaresi kullanılır.

 

Tüm sayısal veri türlerinde, en yüksek bit işaret bitini temsil eder. Tamsayı türlerinde sayı, 2'ye tümleyen sisteminde yorumlanır. Ondalık sayı türleri ise "IEEE Standart 754 for Binary Floating-Point Arithmetic" dahilindedir. Burada söz konusu ondalık sayı türlerinin bit düzeyindeki temsillerine yer vermeyeceğim.

 

4. İşaretçi Veri Türleri (Pointer Data Types)

 

Bellek üzerindeki konumları gösteren veri türlerine işaretçi denir. IA-32 mimarisinde iki tür işaretçi tanımlıdır: Yakın İşaretçi (NEAR POINTER, 32 Bit) ve Uzak İşaretçi (FAR POINTER, 48 Bit). Yakın işaretçi, üzerinde çalışılan segment değiştirilmeksizin bellekteki bir noktanın gösterilmesidir ve FLAT bellek modelinde kullanılan tek türdür. Parçalı (segmented) bellek modelinde ise, gösterilmek istenen veri eğer üzerinde çalışılan segment dahilinde değilse uzak işaretçiler yardımıyla bunun gösterilmesi gerekir. Bu veri türünün kullanımına pek az rastlanır. Genel olarak 16 bitlik platformda kesme vektörlerinin çağırılmasında ve dolaylı adreslemede; 32 bitlik platformlarda işletim sisteminin kullandığı yönetimsel komutlarda ve kesmelerde uzak işaretçiler kullanılır. Bellekteki hücrede yer alan değer, hedeflenen segment işaretçisi(16 bit) ve offset konumudur(32 bit).

 

5. BCD Veri Türü ve Paketlenmiş BCD Tamsayılar (Binary Coded Decimal and Packed BCD Integers)

 

BCD veri türü, 4 bit ile ifade edilen ve 0-9 arasındaki rakamları gösteren türdür. ifadede ve anlaşılırlıkta kolaylık sağlaması için ve onluk düzene benzerliğinden ötürü kullanılmaktadır. Bir BCD rakamının ifade edilmesi için 4 bit gerektiğinden, bir baytlık büyüklükteki bir BCD sayının yüksek 4 bitinin anlamı olmayacaktır. Ancak belleği ekonomik kullanmak adına bu yüksek 4 bite de anlam yüklenmesi durumunda bu yapıya Paketlenmiş BCD Tamsayı (Packed BCD Integer) denir. IA-32 mimarisinde 10 bayta kadar paketlenmiş BCD tamsayılar tanımlamak mümkündür. Bu şekildeki bir yapıda ancak ilk 9 bayt sayısal olarak anlamlı, dizideki 80. bit ise tamsayının işaretini belirtmektedir. Bu şekilde 18 basamaklı ve işaretli tamsayılar ifade etmek mümkün olmaktadır. Mimaride paketlenmiş baytlar üzerinde işlem yapan temel set komutları ve daha uzun paketler üzerinde işlem yapan SSE komutları bulunmaktadır.

 

Yukarıda açıklanan tüm bu veri türlerinin yanında bit alanı, bit dizisi ve SIMD(Single Instruction Multiple Data) verileri bulunmaktadır. Bitler ile ilgili işlem yapan test ve atama komutları temel set dahilindedir. SIMD veri türü ise MMX ve SSE olmak üzere ikiye ayrılır. MMX yazmaçları MM0'dan MM7'ye kadar 64 bitlik yazmaçlar, SSE yazmaçları ise XMM0'dan XMM7'ye kadar olan yazmaçlardır. Bu yazmaçlar özel amaçlı olduğundan işlemci dahilindeki çalışma yazmaçları arasında gösterilmez. SIMD yazmaçlarının hemen hemen tamamı paketlenmiş bellek verileri üzerinde işlem yaparlar.

 

Bellek Üzerinde Çalışmak

 

Bellek, bir işlemcinin sınırlı sayıdaki kaynaklarının yetmediği durumlarda kullanılmak üzere hazır bulunan ve işlemci tarafından doğrudan erişilebilen fiziksel bölgedir. Bilgisayarın icadından bu yana geliştirilen hemen hemen her uygulama ve kod, çeşitli verileri işleme tabi tutabilmek amacıyla geçici depolama alanlarına ihtiyaç duymuştur. Bugünkü uygulamaların bellek ihtiyaçları ise ortalama olarak 40-50 MB arasındadır.

 

32 bit assembler'da FLAT bellek modelinde erişilen bellek offset'tir. Herhangi bir offsetin ifadesi için aşağıdaki taslak biçim ele alınabilir:

 

Offset = Base + ( Index * Scale) + Displacement

 

Bu yapıdaki öğelerin açıklaması şu şekildedir:

 

Base: Herhangi bir genel amaçlı yazmaç; yani EAX, EBX, ECX, EDX, ESI, EDI, ESP veya EBP'den biri.

Index: ESP dışındaki herhangi bir genel amaçlı yazmaç.

Scale: 1, 2, 4 veya 8.

Displacement: Herhangi bir 32 bitlik sayı.

 

Offset olarak yazılan ifade, işlemci tarafından çözümlenir ve ortaya çıkan sonuca Efektif Adres denir. Bu şekilde esnek bir yapıyla belirtilen bellek erişim modeli sayesinde pek çok yapısal değişkene döngüler dahilinde zahmetsizce erişmek mümkün olmaktadır. Belleğe erişmek için, offset ifadesinin köşeli parantez içerisinde belirtilmesi gerekir. Aksi takdirde bu şekildeki bir yapı, assembler tarafından kodlama hatası olarak algılanır. Aşağıda bellekten yazmaçlara aktarım ile ilgili örnekler mevcuttur.

 

mov eax, [ebx + ecx*4]                      ; EAX yazmacına, EBX + ECX * 4 ile belirtilen adresteki 32 bitlik veri aktarılır.

mul word ptr [edx + ebp]                    ; EDX + EBP yazmacı ile gösterilen adresteki 16 bitlik bellek verisi AX yazmacı ile çarpılır. Sonuç DX:AX çiftinde depolanır.

 

Bu tür adreslemede, FLAT bellek modelinin kullanımından ötürü tüm işaretçi yazmaçları aynı segment üzerinde çalışmakta ve sabit bir yer işaret edilmektedir. Ancak bellek modelinin parçalı (segmented) olması durumunda, base ve index parametrelerinin her ikisi de ESP ve/veya EBP'den oluştuğu takdirde üzerinde çalışılan segment SP olarak kabul edilmektedir. Normalde, aksi belirtilmedikçe varsayılan segment DS'dir. Aşağıdaki kodlama şekli ile, varsayılan segment geçici olarak değiştirilebilir:

 

mov edx, es:[eax + ebx * 8 + 4]

 

Bu kodlamada, sadece bu komut için üzerinde çalışılan segmentin ES olduğu varsayılmaktadır. Dolayısıyla fiziksel adres hesabında ES'nin değeri kullanılır. FLAT bellek modelinde bu kodlamanın herhangi bir etkisi olmadığı halde parçalı (segmented) bellek modelinde, fiziksel adres hesabındaki şu yöntemden ötürü büyük farklılıklar ortaya çıkmaktadır:

 

PA = (SEGMENT << 20) + OFFSET

 

Bu sayede, parçalı bellek modelinde 4GB'a sınırlı işaretçiler ile maksimum 64GB'lık fiziksel bellek erişimi mümkün olmaktadır. 16 bitlik platformda bu hesap şöyleydi:

 

PA = (SEGMENT << 4) + OFFSET

 

Bu hesap yöntemi, 64K'ya sınırlı işaretçiler ile erişilebilecek maksimum belleğin 1MB olmasını sağlıyordu. Her iki yöntemde de toplam erişimin işaretçi boyutuna oranı aynıdır: 16.

 

Normalde işlemcinin hesaplamış olduğu efektif adresin yazmaçlarda saklanabilmesi için işlemci tarafından sunulan bir komut da vardır: LEA (Load Effective Adress). Şu şekilde kullanılır:

 

lea eax, offset

 

Hedef parametre, herhangi bir genel amaçlı yazmaç da olabilir. Bu komutun açıkça yapmış olduğu işlem, offset değerini hedef yazmaca yazmaktır. Offset ile belirtilen belleğe erişim dahi yapılmaz. Bu komut, belirtilen nokta yakınlarındaki bölgeye, sadece displacement değeri değiştirilmek suretiyle fazla sayıda erişim yapılacaksa hızdan kar sağlamak amacıyla kullanılır. Zira işlemcinin karmaşık yapıdaki bir offset ifadesini çözümlemesi zaman almaktadır.

 

Yığın (The Stack)

 

Yığın temel olarak ve önem sırasına göre şu 4 amaca hizmet eder:

Genel amaçlı yazmaçlardan ESP(Stack Pointer) ve EBP(Base Pointer) yığın üzerinde çalışmaktadır. Yığın üzerinde işlemcinin yaptığı işlemler ESP'nin değişmesine yol açar. Aynı şekilde, programcının yığın üzerinde serbestçe çalışabilmesi için EBP'yi değiştirmesi gerekir.

 

Bilindiği üzere alt programların çağırılması için CALL komutu kullanılmaktadır. Bu komutun çalışması esnasında EIP değeri, ESP'nin göstermiş olduğu konumdan itibaren aşağıya doğru 4 baytlık alana yazılır. Bu sayede, alt programı çağıran üst programın çalışma konumu korunmuş olur. Ardından da EIP'ye, parametre olarak belirtilen yeni değer yüklenir ve çalışmaya o noktadan devam edilir. Bu komutun zıttı olan RET komutu ise, ESP'nin göstermiş olduğu konumdan itibaren yukarı doğru 4 baytlık alanı EIP yazmacına aktarır ve işlemcinin çalışma noktasını değiştirir. Burada dikkat edilmesi gereken en önemli kural, yığının düşük adreslere doğru büyüdüğü ve yüksek adreslere doğru küçüldüğüdür.

 

Benzer şekilde, işlemci dahilindeki kesmelerin çağırılması için kullanılan INT komutu da, çalışma noktasının değişecek olmasından ötürü EIP yazmacını yığına atar. Ancak kesme çağrımında bir farklılık da söz konusudur: Yazılım kesmelerinin programcı tarafından kontrollü biçimde çağırılmasına rağmen donanım kesmelerinin ne zaman ve hangi işlem yapılırken meydana geleceği önceden kestirilemez. Dolayısıyla EFLAGS yazmacının konumuna göre işlem yapacak olan bir kod, kesme rutininin yapacağı değişikliklerden ötürü yanlış çalışabilir. Her iki tür kesmenin de dönüşü IRET komutuyla sağlandığından, her iki türlü çağrım işleminde de EFLAGS yazmacının değerinin korunması gerekir. Aynı zamanda kesmeler, uzak adresleme yöntemiyle çağırılırlar. Dolayısıyla yığına önce EFLAGS, sonra CS, sonra da EIP yazmaçları atıldığından toplam 10 baytlık bir kullanım söz konusu olacaktır. IRET işlemi, yığına atılan yazmaçları ters sırada geri alır ve çalışmanın eski konumdan devamını sağlar.

 

Alt programlara parametre aktarımı 16 bit assembler'da olduğu gibidir. Dolayısıyla bu konu hakkında detaylı bilgi edinmek isteyenler, bu yazı dizisinin 3. bölümü olan "Turbo Assembler ile Çalışmak" konusunun ikinci yarısındaki "Alt Programlar" başlıklı bölümü inceleyebilirler.

 

Alt programlar dahilinde iki tür yerel değişken söz konusu olabilir: Parametre olarak aktarılanlar ve yerel olarak tanımlananlar. Bu değişkenlerden parametre olarak aktarılanlar, zaten üst program tarafından yığın veya yazmaçlar üzerinden gönderilmiştir. Dolayısıyla önemli olan yerel olarak tanımlananlar için ayırılacak bölgedir. Şu bir gerçektir ki çoklu görev dahilindeki her bir görevin kendine ait yazmaçları ve belirli büyüklükte yığını vardır. Bu yığın, tamamen programcının denetimi altındadır. Dolayısıyla, yığının kullanılmayan bölgelerinin söz konusu yerel değişkenleri depolamak amacına hizmet etmesi akıllıca olacaktır. Yani, EBP'nin ESP'nin son konumuna eşitlenmesinin ardından, artık EBP'nin göstermiş olduğu değerden küçük olan adres bölgeleri yerel değişkenleri barındırmak amacıyla kullanılabilir. Aşağıdaki örnekte bu duruma değinilmektedir.

 

0004FFFC

Parametre 1

0004FFF8

Parametre 2

0004FFF4

Parametre 3

0004FFF0

Parametre 4

0004FFEC -> ESP

Eski EIP

0004FFE8

?

0004FFE4

?

0004FFE0

?

0004FFDC

?

0004FFD8

?

0004FFD4

?

0004FFD0

?

0004FFCC

?

0004FFC8

?

0004FFC4

?

0004FFC0

?

0004FFBC

?

0004FFB8

?

0004FFB4

?

0004FFB0

?

 

Görüldüğü üzere ESP'nin değeri, üst program tarafından 4 adet parametrenin yığına atılması ve ardından alt programın çağırılması sonucu 20 bayt azalmış ve 0004FFEC'ye gelmiştir. Şu kesindir ki alt programın bu yığın üzerinde ihtiyaç duyacağı bölge, ESP'nin şu anki değerinin 4 fazlasından itibarenki 16 bayttır. Buradaki parametrelere erişim için aşağıdaki ilk atama yapılmalıdır:

 

push ebp                ; 32 bitlik EBP yazmacı yığına atılıyor. ESP 4 azalır.

mov ebp, esp          ; ESP, EBP'ye kopyalanıyor.

 

Artık ESP'nin değeri 0004FFE8'dir. Bu noktanın yukarısı kritik önem taşır ve genellikle sadece okunmalıdır. Ancak, üst program tarafından aktif olarak kullanılmayacağı bilindiğinde aşağı bölgenin hiçbir önemi yoktur. Alt programın, üzerinde işlem yapmak ve sonuç saklamak için 32 baytlık yerel belleğe ihtiyaç duyduğu varsayılsın. Bu durumda 0004FFC8 adresinden 0004FFE8 adresine kadar olan 32 baytlık bölge güvenli biçimde kullanılabilir. Bu bölge üzerinde güvenli işlem yapmak için EBP yazmacı kullanılmalıdır. Dolayısıyla 0004FFC8 adresi, [EBP - 32] biçiminde gösterilmelidir(32d = 20H).

 

Yazmaçların veya kimi verilerin değerlerini geçici olarak muhafaza etme işlemi, PUSH ve POP komutları ile klasik olarak yapılan işlemlerdir. Dolayısıyla özel önem teşkil etmezler. Bu noktada dikkat edilmesinin önem taşıyabileceği en büyük unsur, bu komutların 16 veya 32 bitlik yazmaç, bellek veya mutlak verileri taşıyabildikleridir. Dolayısıyla, 32 bitlik veriler üzerinde işlem yapılan bir uygulamada, yığın işaretçisinin kazara 32 bitlik doğal sınırlar dışına çıkarılması başarımın azalmasına yol açacaktır. Olası tehlikelerin önüne geçmek amacıyla, doğal hizayı bozabilecek özel durumların ardından ESP'nin yeniden hizalanması gerekir. Aksi takdirde işlemcinin çalışmasında hiçbir teknik hata olmadığı halde kodun işleyişi yavaşlayabilecektir.

 

 

 

Bu bölümün 2. kısmına burada nokta koyuyorum. Çünkü bundan sonraki başlıklar, doğrudan kod yazımı ve teknikleri ile ilgili olacak. Sonraki yazımı yayınlayana dek ilgilenen arkadaşların bu yazıdaki başlıklar dahilinde soru işaretleriyle kalmamasını ümit ediyorum. Kafanıza herhangi bir şey takıldığında bunu grup üzerinden sormakta tereddüt etmeyin. Çünkü soracağınız soru, kimilerinin sormaya üşendiği bir soru da olabilir. Dolayısıyla herhangi bir zahmete girmeden daha fazla kişiye faydalı oluruz. Yazmış olduğum bu teknik makaleyi bilgisel hatalara karşı incelemiş olmama rağmen, tespit edebileceğiniz hatalardan ötürü şimdiden herkesten özür diliyorum. Hepinize iyi günler ve çalışmalar dilerim.

 

Cihan Atıl Namlı