Microsoft 32 Bit Macro Assembler'da Kodlama

 

Gerek bu bölümde anlatılacak olan Microsoft ürünü assembler'da, gerekse de öteki benzer nitelikli assembler'larda kodlama, pek çok gelişkin nitelikli üst düzey programlama dilinde olduğu gibi derleyiciye ve sisteme gönderilecek komutların da bulunduğu farklı kısımlarda ele alınır. Microsoft Macro Assembler'daki (kısaca MASM olarak kullanılacaktır) kodlama tekniği, bu yazı dizisinin 3. bölümünde ele alınan Turbo Assembler'dakine kısmen benzemektedir. Dolayısıyla bir yanda söz konusu bölümün de açık tutulması faydalı olabilir. MASM'a uygun bir ASM dosyasının içeriği, amaç ve yöntemlerine göre şu 6 bölümden oluşmaktadır:

Bir assembler programında, yukarıda belirtilen maddeler dahilinde tüm öğelerin bulunması gerekmese de, kaynak dosyanın derlenebilmesi için en azından platform, model belirtilmesi ve kod bulunması gerekir. Daha açıklayıcı olması için aşağıdaki örneğin incelenmesinde fayda vardır. Bu örnek, derlenip bir EXE dosyası oluşturulabilecek en salt kodu temsil etmektedir.

 

.386

.model flat, stdcall

 

.code

 

start:

 

ret

 

end start

 

İlk satır platformu, ikinci satır model ve dili, kalan bölüm ise kodu temsil etmektedir. Bu kod hiçbir iş yapmamakta, program çalıştırılır çalıştırılmaz sona ermektedir. Konu ilerledikçe, yeni konu başlıkları altında daha nitelikli ve işe yarar kodlar geliştirilecektir.

 

Bir ASM dosyasının içeriği hakkında unutulmaması gereken en önemli şeylerden biri, ifadelerin satır satır işlendiğidir. Yani C/C++'ta veya Pascal'da olduğu gibi ifadelerin (;) gibi herhangi bir karakterle sonlandırılma durumu yoktur.

 

Aşağıdaki başlıklar altında, assembler dosyası içeriğinin bölümleri oldukça ayrıntılı biçimde irdelenmektedir.

 

Platform, Model, Dil ve Derleyici Parametreleri

 

Bu başlık altında anlatılan parametreler, EXE dosyasında kod olarak dönüştürülmezler. Sadece derleyiciye kodun nasıl değerlendirileceğini belirtirler.

 

Platform

 

.platform

 

Platform, derleyicinin kaynak kodu nasıl ve hangi işlemciye göre değerlendireceğini belirten parametredir. Bilindiği üzere Intel I286 ve I386 işlemcileri arasında büyük farklılıklar vardır. 80386 işlemcisinin kaynakları kullanılarak yazılan bir kodun 80286 işlemcisinde çalışabilmesi mümkün değildir. Dolayısıyla bu parametre hayati önem taşımaktadır. Bu parametre sayesinde derleyici, kullanılacak komut setini ve bellek erişimini tayin eder. (.) karakterine bitişik olarak yazılabilecek şu ibareler mümkündür: 8086, 186, 286, 386, 486, 586, 686. Adlarından kolayca anlaşılabilecek bu komutlardan sadece 586 ve 686'nın anlamı yaygınca bilinmediğinden belirtilmesinde fayda görüyorum: 586 = Pentium, 686 = Pentium Pro.

 

Intel işlemcilerinde, ondalık sayı işlemlerini gerçekleştiren birime Floating Point Unit (FPU) denir. "Math CoProcessor" olarak da adlandırılan bu birim, 80286 işlemcisinden itibaren işlemci kılıfı dahilindedir ve birim adı, işlemci adının 1 fazlasıdır. x87 FPU'ya ilişkin komut seti, işlemci komut setine paralel olarak gelişim göstermiştir ve işlemciden işlemciye değişmektedir. Dolayısıyla, kullanılacak FPU komut setine ilişkin platform tanımlaması da mümkündür. Aynı şekilde (.) karakterine bitişik olarak yazılabilecek şu değerler mümkündür: 8087, 287, 387, NO87. Buradaki değerlerden NO87, FPU komutlarının derlenmesini önler. Bu durumda, derleyici komut satırı parametrelerinden emulation(olmayan bir kaynağın görevinin yerine getirilmesi) belirtilmeli ve ilgili kütüphane dahil edilmelidir.

 

Model

 

.model bellekmodeli

 

Bilindiği üzere x86 işlemcisinde veri, kod ve yığın amaçlarına hizmet eden farklı segment yazmaçları mevcuttur. Tüm bu segmentler, tüm belleği paylaşarak adreslerler. Ancak amacı ve kullanım sınırı belli olan bir programın tüm belleği serbestçe kullanabilmesi mümkün değildir. Bu yüzden program segmentlerinin belirli değerler ile sınırlı olarak kullanılması gerekir. Model parametreleri, kod ve verinin bellek üzerinde ne şekilde yer alacağını belirler.

 

Bu yazı dizisinin 3. bölümünde, 80286 işlemcisi için önemli olan 6 bellek modelinden söz edilmişti: Tiny, Small, Medium, Compact, Large, Huge. Pratikte gözlenebileceği üzere bu bellek parametreleri, kod ve verinin yakın ya da uzak işaretçiler ile gösterilmesini etkilemektedir. Ancak 32 bitlik işlemcinin yakın adresleme bölgesi 4GB olduğundan bellek ihtiyacı fazlasıyla karşılanabilmekte ve dolayısıyla uzak işaretçilere gerek kalmamaktadır. Hem kodun, hem de verinin aynı yakın adresleme bölgesi dahilinde olduğu Tiny bellek modeli, bu platform için uygundur. Ancak bunun yerine, tüm işlemlerin segment yazmaçlarından bağımsızlaşmasını sağlayan Flat bellek modeli kullanılır. Yukarıdaki en salt kod örneğinde bu kullanım görülmektedir.

 

Dil

 

.model dil

 

Burada dilden kasıt, alt programların(fonksiyonların) varsayılan çağrı yöntemidir. Bilindiği üzere assembler'da alt programların çağırılması esnasında gönderilecek parametreler genellikle yığına atılır. Burada parametrelerin atılış sırası, erişimde kullanılan yazmaçların muhafazası, yığının eski haline getirilmesi gibi unsurlar önem taşımaktadır. Yaygın olarak kullanılan çağrı yöntemleri şunlardır: C, SYSCALL, STDCALL, BASIC, FORTRAN, PASCAL. Aşağıdaki tablo, bu çağrı yöntemlerinin özelliklerini göstermektedir:

 

 

C

SYSCALL

STDCALL

BASIC

FORTRAN

PASCAL

Ön altçizgi

X

 

X

 

 

 

Tamamı büyük harf

 

 

 

X

X

X

Parametreler soldan sağa

 

 

 

X

X

X

Parametreler sağdan sola

X

X

X

 

 

 

Çağıranın yığını düzeltmesi

X

 

*

 

 

 

(E)BP'nin otomatik korunması

 

 

 

X

X

X

Çoklu parametre aktarımı

X

X

X

 

 

 

*Sadece çoklu parametre aktarımında.

 

En yaygın kullanılan tür STDCALL'dır. Windows'un kendi uygulamaları, kütüphaneleri ve pek çok durumda Microsoft Visual C++ da bu yöntemi kullanmaktadır. Dolayısıyla, özel bir amaç veya uyumluluk güdülmediği sürece bu parametrenin stdcall olarak kullanılmasını öneririm.

 

Örnek olarak verilen salt kodda da görülebileceği üzere bellek ve dil parametreleri, .model belirtecini takiben virgül ile ayırılarak yazılabilir.

 

Derleyici Parametreleri

 

.option parametre, parametre...

 

Derleyicide, kaynak kodun farklı şekilde derlenebilmesi için komut satırındaki ibarelere ek olarak yazılabilecek parametreleri mevcuttur. Ancak bu parametreler, kaynak dosya içerisinde de belirtilebilmektedir. Örneğin Windows API'nin doğru biçimde kullanılabilmesi için option casemap: none belirtilmesi gerekir.

 

Dahil Edilecek Dosyaların ve Kullanılacak Kütüphanelerin Belirtilmesi

 

Yüksek seviyeli programlama dillerindeki #include, uses gibi dahil etme komutları MASM'da da bulunur. Bu sayede çeşitli ortak dosyalar kullanılabilir ve kaynak dosyanın boyutu küçülür. Aşağıda buna ilişkin taslak biçim yer almaktadır:

 

include dosyanın tam yolu

 

include d:\masm32\include\windows.inc    gibi...

 

Kaynak dosyalar, işlev prototiplerini, gövdelerini, global tanımlamaları, makroları ve eşitlikleri içerebilir. include komutu ile herhangi bir dosya dahil edildiğinde derleyici, sanki komutun bulunduğu noktada o dosyanın içeriği varmış gibi işlem yapar. Dolayısıyla içerik sırasına dikkat etmek gerekir. Bilhassa segmentlerin yerleşimi önem taşıyabilmektedir. Teknik bir yöntem olarak, include dosyalarının derlendiğinde kod oluşturmayacak yapıda olmalarına dikkat edilir. Yani içlerinde sadece prototipler, makro fonksiyonlar ve global derleyici eşitlikleri bulunmalıdır.

 

Yine yüksek seviyeli programlama dillerinde, başlık dosyası adı altındaki kod dosyalarında alt programların gövdeleri değil, sadece prototipleri yer alır. Program gövdeleri ise .lib uzantılı kütüphane dosyalarında veya .dll uzantılı Windows Dinamik Bağlantı Kütüphanelerinde yer alır(DLL dosyalarının içerisindeki kodun kullanımı için yine .lib dosyası gereklidir). Yukarıdaki örnekte olduğu gibi dahil edilen bir prototip kütüphanesindeki fonksiyonların programda kullanılabilmesi için ilgili kütüphanenin de belirtilmesi gerekir. Aşağıdaki taslak biçim buna ilişkindir:

 

includelib dosyanın tam yolu

 

includelib d:\masm32\lib\user32.lib    gibi...

 

MASM32 kabinesi ile gelen dosyalar arasında include ve lib adı altında dizinler bulunur. Bu dizinlerden include, tüm Windows DLL'lerindeki işlevlerin prototiplerini ve global tanımlamaları, lib ise bu DLL'lerdeki işlevlerin giriş adreslerini içeren kütüphaneleri barındırır. Bizim programlarımızda kullanılacak dosyaların adresleri de genellikle bu dizinler dahilinde olacaktır.

 

Not: Söz konusu dizinlerden lib dizini, Windows DLL'lerine statik yöntemle bağlantı sağlar. Bu dizindeki dosyaların oluşturulabilmesi amacıyla MASM'nin kurulduğu dizindeki include dizininde BLDLIBS.BAT adlı bir toplu iş dosyası bulunur. İçinin incelenmesi suretiyle yeni DLL'lerin dış uzantılı işlevlerine statik bağlantı kütüphaneleri kolayca oluşturulabilir.

 

Eşitlikler, Global Tanımlamalar ve Makro İşlevler

 

Eşitlikler

 

MASM dosyasının bu bölümü, önceki bölümlere benzer olarak kod üretmeyen, ancak kod üretilmeden önce yapılacak yer değişimlerini hedefleyen bir bölümdür.

 

Yüksek seviyeli programlama dillerinden de aşina olunacağı üzere #define önişlemci komutu ile derleme zamanında kullanılabilecek eşitlikler tanımlanabilmektedir. MASM'de aynı amaca hizmet etmek üzere şu belirteç mevcuttur:

 

hedef ifade    equ    kaynak ifade

 

PI    equ    3.14

sayi    equ    25

wsprintf    equ    <wsprintfA>    gibi...

 

Bu belirteç ile yapılan eşitlikler, dosyanın derlemesi esnasında geçici bir kopyadaki tüm hedef ifadelerin yerine, belirtmedeki kaynak ifadelerin yazılmasını ve derlemenin bu işlemi takiben devam etmesini sağlar. Dolayısıyla hedef ifadenin tekil olması dışında kaynak ifade kısıtlaması yoktur. < > operatörleri ile kapalı olması koşuluyla kompleks bir ifadeye de yer verilebilir.

 

Bunun yanı sıra, yine aynı şekilde kullanılan textequ belirteci ile de eşitlik tanımlanabilmektedir. Bu belirtecin bir üstünlüğü, kaynak ifadedeki çeşitli makroları çözümleyerek hedef ifadeye atamasıdır. Bu çözümleme, derleme esnasında yapıldığı için üretilen programın kullanımı esnasında bir devingenlik taşımaz. Aşağıdaki örneklerde bu çözümlemeye değinilmektedir.

 

libpath    textequ    @Environ(<LIB>)    ;    Bu eşitleme ile libpath ifadesine, sistemdeki varsayılan kütüphane dizin yolu atanmıştır. "D:\Masm32\lib" gibi...
uzunluk   
textequ    %(endadr-staradr)    ;    Bu eşitleme ile toplam kod uzunluğu, uzunluk ifadesine atanmaktadır. Kaynak ifade, sayısal bir ifadeye çözümlenmektedir.

 

Buradaki eşitlikleri değişkenler ile karıştırmamak gerekir. Hedef ifadeler, dosya içerisinde sadece "placeholder" olarak adlandırılan biçimde yer tutarlar ve derleyici sadece kaynak ifadeyi görür. Dolayısıyla hedef ifade için adres veya büyüklük gibi nicelikler söz konusu değildir.

 

Global Tanımlamalar

 

Yine yüksek seviyeli dillerdeki struct, union ve typedef belirteçleri, MASM'de mevcuttur. Kullanım biçimleri aşağıdaki gibidir:

 

hedef yapı    struct

    yapı üyesi    veri türü    ?

    yapı üyesi    veri türü    ?

    ...

hedef yapı    ends

 

dikdortgen    struct

    x1    dd    ?

    y1    dd    ?

    x2    dd    ?

    y2    dd    ?

dikdortgen    ends    gibi...

 

Bu örnekte dikdörtgen, sol üst köşesine ve sağ alt köşesine ait koordinatlar aracılığıyla 2 boyutlu düzlemde tanımlanmaktadır. Yapı içerisindeki her bir üyenin veri türü dd(define doubleword) olduğundan yapının toplam boyutu 16 bayttır.

 

hedef ortak yapı    union

    ortak1    veri türü    ?

    ortak2    veri türü    ?

    ...

hedef ortak yapı    ends

 

ogrenci    union

    ogrencino    db    10 dup(?)

    tckimlikno    db    12 dup(?)

ogrenci    ends    gibi...

 

Yukarıdaki örnekte bir öğrenci, her ikisi de kendine özgü olacağı için öğrenci numarası veya T.C. kimlik numarası ile tanımlanabilmektedir. Dikkat edilecek olursa her iki alanın da boyutu birbirinden farklıdır. Bu ortak yapının boyutu, içerdiği alanların en büyüğü olan 12 bayttır.

 

hedef tür    typedef    önceden tanımlı tür

 

ondalik    typedef    real4

tamsayi    typedef    dword    gibi...

 

Yukarıdaki örneklerde, yazacağımız programlarda anlaşılırlık açısından kullanabileceğimiz türkçe veri türleri, amaçlarına yönelik olarak önceden tanımlı veri türlerinden türetilmiştir. MASM'da kullanılabilecek varsayılan veri türlerine ileriki alt başlıklarda değinilecektir.

 

Yapı üyelerine kod ile erişim, yüksek seviyeli dillerde olduğu gibi (.) operatörü ile gerçekleştirilir. Bu açıklanan türler dışında record belirteci ile de bit düzeyinde alan tanımlamaları yapılabilmektedir. Aşağıdaki şekilde kullanılır:

 

hedef tür    record    alanadı1: uzunluk1 [=öndeğer] [, alanadı2: uzunluk2 ...]

 

zaman    record    saniye: 5, dakika: 6, saat: 5

 

Yukarıdaki örnek, MS-DOS'taki FAT16 dosya sisteminde, dosya tarihlerinin kodlamasını göstermektedir. Bu örnekte saati göstermek için 5 bit kullanılarak 0'dan 23'e değerler sağlanmış; dakikayı göstermek için ise 6 bit kullanılarak 0'dan 60'a değerler sağlanmıştır. 2 bayta değin kalan 5 bit ile saniyeyi tam hassasiyette (0'dan 60'a) göstermek mümkün değildir. Sadece çift sayılı saniyeleri göstermek suretiyle (2^5=32 > (60/2 = 30)) bu gösterim 2 bayta sığdırılmıştır. Buradaki hassasiyet kaybı yaklaşımı tam bir mühendislik modellemesidir, zira 1 saniye ihmal edilebilecek bir büyüklüktür. Daha öte ve güvenlikli bir modelleme örneği olarak da, zaman kaydının 3 bayt ile yapılarak 10ms'lik hassasiyete ulaşmak mümkün olabilir(saniye tam hassas kaydedilir, geri kalan 7 bit ile 0-100 arası değerler elde edilir).

 

Makro İşlevler

 

Makro işlevler, yine yüksek seviyeli dillerdeki #define önişlemci komutuyla tanımlanan makro işlevler ile aynı niteliktedir. Ancak gerek MASM'de, gerekse de bu amaca yönelik öteki assembler uygulamalarındaki makro derleyebilme özellikleri çok gelişmiştir. Değişik nitelikte parametreler alabilme, buradaki en önemli özelliklerdendir. Aşağıda verilen örnekler, MASM'deki makro işlev kullanımının sağladığı kolaylıkları göstermek üzere hazırlanmış sık kullanılan ifade gruplarıdır.

 

makro adı    macro    parametre tanımlamaları

    makro yerel değişkenleri -> LOCAL

    makro gövdesi

    ...

endm

 

szText MACRO Name, Text:VARARG ; Bu makro, Name adıyla tanımlanan bir dizeyi oluşturur. Program kodunun içerisinde herhangi bir yerde kullanılabilir.
    LOCAL lbl
    jmp lbl
    Name db Text,0
    lbl:
ENDM


m2m MACRO M1, M2 ; Bu makro, M2 ile belirtilen yazmaç/bellek verisini M1 ile belirtilen yazmaca/belleğe kopyalar. Bellekten belleğe kopyalama için sıklıkla kullanılır.
    push M2
    pop  M1

ENDM

 

return MACRO arg ; Bu makro, C dilindeki return(arg) ifadesini C++'taki parantezsiz biçimiyle temsil eder. Görülebileceği üzere parametre, her türlü veri olabilir.
    mov eax, arg
    ret

ENDM

 

Yukarıdaki makro işlevleri, yazacağınız tüm assembler programlarının başına yerleştirmenizi tavsiye ederim. Mutlaka işinize yarayacaktır. Makro işlev konusu çok ayrıntılı olup, burada daha fazla yer vermeyi düşünmüyorum.

 

Alt Program Prototipleri

 

MASM'da yazılacak alt programların çağırılmasının iki yöntemi vardır. Birinci yöntem, kullanılacak işlev çağrı yöntemine dikkat etmek suretiyle parametreleri göndermek ve CALL komutu ile işlev adresini çağırmaktır. Standart olarak kullanılan stdcall yönteminde önce parametreler sondan başa doğru yığına atılır, daha sonra işlev adresine dallanılır. Ancak dilin stdcall olmaması durumunda, bu yazının başlarında verilen tablodaki kuralları iyi bilmek ve çağrıyı ona göre yapmak gerekir. Çünkü mikroişlemci sistemlerinde en küçük hata dahi görevin, programın ve hatta işletim sisteminin durmasına yol açabilir.

 

Zaten bir ASM dosyasının bölümlerinden "Platform, Model, Dil ve Derleyici Parametreleri" bölümü, bu çoklu standarda rağmen programcının kullanım biçimini standartlaştırmaya yöneliktir. MASM ve benzer pek çok modern assembler'da işlev çağrımı için invoke komutu kullanılır. Bu komutun devamına önce işlevin adı, takiben de işlevin parametreleri yazılır. Alt program prototiplerinin tanımlanması, bu yöntemi kullanarak yapılacak çağrılarda parametre sayı ve tür hatalarını önlemeye yöneliktir. Aşağıda, bu tanımlamaya ilişkin taslak ve örnekler yer almaktadır.

 

işlev adı    proto    [uzaklık]    [işlevin çağrı dili]    [[parametre1] : veri türü] [, [parametre2] : veri türü] [, ...]

 

Yukarıdaki parametrelerden uzaklık, işlevin segmentasyonu ile ilgilidir. Geçerli parametre türleri near, far, near16, near32, far16 ve far32 'dir. Bu parametre belirtilmediğinde assembler, işlevin uzaklığını kendi belirler. Flat bellek modelindeki uzaklık near32 'dir. İşlevin çağrı dili ise, "Platform, Model, Dil ve Derleyici Parametreleri" başlığı altında incelenen tablodaki dillerden herhangi biridir. Sadece özel amaçlar söz konusu olduğunda veya dış bir işlevin çağrı yönteminin farklı olması durumunda belirtilmesi önerilir. Bu parametre belirtilmediğinde assembler, dosyanın başındaki dil seçimini varsayılan olarak kabul eder.

 

Bu şekilde tanımlanan bir işlevin basit çağrı yöntemi:    invoke    işlev adı [, parametre1, parametre2, ...]

 

Parametre almayan işlev:    Func0    PROTO

1 dd parametre alan işlev:    Func1    PROTO    :DWORD

2 dd parametre alan işlev:    Func2    PROTO    :DWORD, :DWORD

Çağrı yöntemi pascal, 2 dw ve 1 dd parametre alan işlev:    Func3    PROTO    pascal    :WORD, :WORD, :DWORD

 

Yukarıdaki örneklerde, parametrelerin varsayılan adlarını belirtmeye gerek duyulmamıştır; zira kodun derlenmesi üzerinde hiçbir etkisi yoktur. Ancak anlaşılırlığı arttırmak adına belirmek istenirse (:) operatörünün öncesinde yazılabilir: param1: DWORD gibi.

 

Program prototip tanımlaması, alt program adreslerinin oluşturulmasını ve sabitlenmesini sağlar. Bu sayede kodun herhangi bir yerinde herhangi bir yerde tanımlı alt program serbestçe çağırılabilir. Aksi takdirde, derleme işlemi doğrusal ve nedensel bir süreç olduğundan, söz konusu alt programın gövde tanımlamasına ulaşılmadan o bölümün çağırılması mümkün olmayacaktır.

 

Genel Değişken ve Yığın Tanımlamaları

 

Genel değişkenler, bilineceği üzere kodun derlenmesi esnasında değerleri ve büyüklükleri, işlem sonucunda oluşturulacak ikili dosyaya yerleştirilen, programın her noktasından serbestçe erişilebilen ve aksi belirtilmedikçe değiştirilebilen bellek bölümüdür. Genel değişkenler bölümünde tanımlanabilecek türler, assembler tarafından sunulan ön tanımlı veri türleri ve genel tanımlamalar ile oluşturulabilecek kompleks veri türleridir.

 

Assembler programlarında genel değişken olarak tanımlanacak veri türlerinin bulunabileceği farklı bölümler mevcuttur. Genel olarak, söz konusu değişkenlerin tanımlanacağı bölümler şu şekildedir:

 

.bölüm1

 

değişken1    tür1    [? | öndeğer]

değişken2    tür2    [? | öndeğer]

...

 

.bölüm2

 

değişken3    tür3    [? | öndeğer]

değişken4    tür4    [? | öndeğer]

...

...

 

Bu bölümler, fiziksel segment yerleşimlerinden (cs, ds, es, fs gs, ss) farklı olarak, derleme esnasında farklı prosedüre tabi tutularak bellek üzerindeki yerleşimleri (RVA - Relative Virtual Address) değişir. Ayrıca yazı dizisinin 5. bölümünün ilk parçasında da belirtildiği üzere bellek bölümlerinin okunma, yazılma ve çalıştırılma izinleri mevcut olduğundan, söz konusu bölgenin erişimini de belirler. Her yeni bölüm, bir öncekinin sonunu işaret eder. Dolayısıyla kendisi de bir bölüm olacak kod ile birlikte bu bölümler, dosyanın en alt bölümünde yer almalıdır. Dosyanın başında belirtilebilecek .SEQ ibaresi ile bu bölümlerin yazım sırasında yerleştirilmesi (ki hiçbir ibare belirtilmediğinde varsayılan budur), .ALPHA ibaresi ile de alfabetik sırada yerleştirilmesi sağlanabilir, ancak kodun işleyişi üzerinde etkisi yoktur. Aşağıda, MASM'da geçerli olan bölümlerin açıklamaları verilmiştir.

 

.data

 

Programdaki ön değeri verilmiş genel değişkenlerin yer alacağı bölümdür. ASM dosyasının derlenmesi esnasında, hedef çalıştırılabilir dosyada bu değişkenlerin kapladıkları yer ayırılır. Yani bölümün büyüklüğü dosya büyüklüğünü doğrudan etkiler. PE teknik açıklamasında bu bölümün karşılığı .data 'dır. .data bölümünün izinleri okuma ve yazmadır.

 

.data?

 

Programdaki ön değeri verilmemiş genel değişkenlerin yer almasının gerektiği bölümdür. Bölüm dahilinde ne kadar çok veya ne büyüklükte değişkenler tanımlanırsa tanımlansın, çalıştırılabilir dosyanın boyutunu etkilemez, sadece sonraki bölümlerin yerleşimini etkiler. Bu bölümde ön değerli değişkenler de yer alabilir, ancak hedefteki çalıştırılabilir dosyanın boyutunu arttırır. Çünkü PE biçimli dosyaların boyutları 512'nin katları biçimindedir. Dolayısıyla bu bölüme yazılacak ekstradan 1 ön tanımlı değişken dahi dosya boyutuna 512 bayt ekleyecektir. Assembler ile üretilen programların boyutlarının oldukça küçük olduğu göz önünde bulundurulursa, alan ekonomisi adına bu tip hatalardan kaçınmak gerekir. PE teknik açıklamasında bu bölümün karşılığı .bss 'dir. .data? bölümünün izinleri okuma ve yazmadır.

 

.const

 

Yüksek seviyeli dillerdeki const ön eki ile tanımlanan değişkenlerin yer alacağı bölümdür. Bu bölümde tanımlanan değişkenlere sadece okuma izni verilir. Genellikle bilimsel sabitlerin, hata ayıklama dizin bilgilerinin yer almasının uygun olacağı bir bölümdür. .const bölümünün .data bölümünden esasında bir farkı yoktur, sadece programcı açısından önlem teşkil eder. Dikkatli ve ne yaptığını bilen bir programcının böyle bir bölüm tanımlama ihtiyacı olmayabilir. PE teknik açıklamasında bu bölümün karşılığı .rdata 'dır. .const bölümünün izni sadece okumadır.

 

.stack

 

Uygulamaya tahsis edilecek yığının belirtildiği bölümdür. Bu bölüm, genellikle değişken tanımlaması için kullanılmaz, ancak ibarenin hemen yanına yazılacak boyut, uygulamanın yığın boyutu olarak tahsis edilir: .stack 2048 gibi. PE teknik açıklamasında bu bölümün bir karşılığı yoktur. .stack bölümünün izinleri okuma ve yazmadır.

 

.code

 

Program kodunun yer alacağı bölümdür. Programın başlangıç kodu ve tüm alt programlar, bu bölümün içerisinde yer almalıdır. PE(Portable Executable) teknik açıklamasında bu bölümün karşılığı .text 'tir. .code bölümünün izinleri okunma ve çalıştırılmadır.

 

PE teknik açıklamasında, burada adı geçmeyen 5 bölüm daha bulunur: .rsrc, .edata, .idata, .pdata ve .debug. Ancak bu bölümler, MASM tarafından gerek derleyici parametreleri ile, gerekse de dosya dahilindeki ibareler yardımıyla kendiliğinden oluşturulur.

 

Bölümler dahilinde tanımlanabilecek verilerin boyut ve nitelik türleri ayrıca önem taşımaktadır. Yukarıdaki örnekte belirtilen tür1, tür2, tür3... gibi ifadeler, MASM'nin bölüm oluşturma boyutunu ve değer tahsis biçimini doğrudan etkileyecektir. Aşağıdaki tabloda bu türlere ve özelliklerine yer verilmektedir. Verilerin sayısal tanım aralıkları için yazı dizisinin bu bölümünün ikinci kısmındaki "Temel Veri Türleri" başlığını inceleyebilirsiniz.

 

Veri Türü

Kısa İfade

Boyut

C/C++ Karşılığı

byte

db

8 bit

char

sbyte

---

8 bit

unsigned char

word

dw

16 bit

short

sword

---

16 bit

unsigned short

dword

dd

32 bit

int

sdword

---

32 bit

unsigned int

fword

df

48 bit

---

qword

dq

64 bit

__int64*

tbyte

dt

80 bit

---

real4

---

32 bit

float

real8

---

64 bit

double

real10

---

80 bit

long double

*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.

 

Yukarıdaki tablolarda yer alan bilgilere dayanarak aşağıdaki örnekleri incelemekte fayda vardır.

 

bayt_degisken    db    ?    ; Ön değeri verilmeksizin 1 baytlık bayt_degisken değişkeni tanımlanmıştır. Bu değişkenin .data? bölümünde tanımlanması uygundur.

 

word_degisken    dw    35    ; Ön değeri 35 olarak atanmak suretiyle 2 baytlık word_degisken değişkeni tanımlanmıştır. .data bölümünde tanımlanması uygundur.

 

dword_degisken    dd    ?, 5125, 0, ?    ; Art arda 4 adet dword değişken tanımlanmıştır. Bunlardan ilk ve sonuncusuna ön değer atanmamıştır. Basit bir dizi tanımlamasıdır. Bu değişkenin .data bölümünde tanımlanması daha uygundur, çünkü ön değerli hücreler vardır.

 

dize    db    "Bu bir assembler programıdır.", 0    ; Bu şekilde metin dizeleri tanımlanabilir. Derleyici, tırnaklar ile kapsanan dizedeki her karakterin ASCII kodunu sıra ile dizer. En sonda bulunan 0 ise, dizenin sıfır ile sonlandırılmış olması için programcı tarafından eklenmelidir. Windows dizeleri bu şekilde kabul ederken DOS, dizeleri $ karakteri ile sonlandırılmış biçimde kabul eder. Bu dizenin .data bölümünde tanımlanması uygundur.

 

float_degisken    real4    10.85    ; Ön değeri 10.85 olan 4 baytlık float_degisken tanımlanmıştır. dword ve real4 veri türlerinin boyutları aynı olmalarına rağmen, değer atamalarındaki standartlar farklıdır. Tamsayı tür olan dword işaretsiz veya işaretli olarak 2'ye tümleyen standardında, ondalık tür olan real4 ise IEEE 754 standardında kodlanarak bellekteki ilgili yere yazılır. Bu değişkenin .data bölümünde tanımlanması gerekir.

 

Genel tanımlamalar bölümünde oluşturulan türlerin, yapıların ve ortaklıkların da değişken olarak tanımlanması mümkündür. Ancak bu türler, içlerinde birden fazla eleman barındırdığından tanımlanma biçiminde < > parantezleri kullanılmalıdır.

 

bolum_ortalamalari    float    16 dup(?)    ; Bu tanımlamada öncelikle daha evvel değinilmiş başlıklardaki typedef belirteci ile tanımlanan float veri türüne yer verilmiştir. Bu tür, söz konusu başlıklarda real4 veri türüne eş güdülmüştür. dup dahili makrosu ile de 16 adet ön değeri olmayan eleman tanımlanmıştır. Yazı dizisinin önceki bölümlerinden hatırlanacağı üzere bu makro, önünde yazan sayı kadar parantez içindeki değerden tanımlamaktadır. Bu değişkenin .data? bölümünde tanımlanması uygundur.

 

arazi    dikdortgen    <0, 0, 512, 384>    ; Bu tanımlamada, önceki başlıklar altında yer verilen dikdortgen yapısı kullanılmıştır. Yapıdaki 4 eleman için de ön değerler verilmiştir. Bu değişkenin .data bölümünde tanımlanması uygundur.

 

msg    MSG    <?>    ; Bu tanımlamada kaynak yapı olarak Windows'taki pencerelere gelen bildirileri barındıracak MSG yapısı kullanılmıştır. Program dahilinde değişkene değer kullanıcı tarafından verilmeyeceği için herhangi bir ön değer de verilmemiştir. Bu sebeple değişkenin .data? bölümünde tanımlanması uygundur.

 

koordinatlar    POINT    <0, 0>, <?, 2>, <?>    ; Bu tanımlamada 3 adet POINT yapısı art arda tanımlanmış; ilkindeki her iki elemana da ön değer verilmiş, ikincisinde ise sadece Y üyesine 2 değeri verilmiştir. Son elemandaki hiçbir üyeye ilk değer verilmemiştir. Bu şekliyle .data bölümünde tanımlanması uygundur.

 

data    WSADATA    64 dup(<?>)    ; Bu tanımlama ile 64 adet ön değersiz WSADATA yapısı tanımlanmıştır. Bu yapı, Windows Sockets içerisinde yer alır. Bu dizinin .data? bölümünde tanımlanması uygundur.

 

Yukarıdaki örnekler ile genel değişken tanımlamanın tüm ayrıntılarına değinilmiştir. Görülebileceği üzere dizi tanımlamak için, değeri tanımlı elemanları virgül ile ayırmak ya da dup makrosunu kullanmak gerekir.

 

MASM'da genel değişken tanımlamaları esnasında kullanılan tamsayı ifadeleri varsayılan ayar olarak 10'luk düzende yorumlanır. Ancak kimi zaman ifadelerin 16'lık veya 2'lik düzende yorumlanmaları, anlaşılırlık açısından önem taşıyabilir. Bu durumda aşağıdaki notasyonlar kullanılabilir:

 

sayi    dd    20h    ; Bu ifade, sayi adlı değişkene 16'lık düzende 20, 10'luk düzende ise 32 değerinin atanmasını sağlar.

bitalani    db    01100010b    ; Bu ifade, bitalani adlı değişkene 2'lik düzende 01100010, 10'luk düzende ise 98 değerinin atanmasını sağlar.

onluksayi    dw    35587d    ; Bu ifade, onluksayi adlı değişkene onluk düzende 35587 değerini atar. Yani yazıldığı gibi okuma koşulunu sağlar.

 

Yani, sayısal ifadenin sonuna eklenecek 'h', 'b' veya 'd' harfleri ile değerin 16'lık veya 2'lik düzende yorumlanması sağlanabilir. Unutulmamalıdır ki bu kural sadece tamsayılar için geçerli olup, ondalık değişkenlerde bu şekilde bir kullanımın üreteceği değer çok farklıdır. Varsayılan yorumlamanın değiştirilmesi için .radix belirteci, dosyanın başında kullanılabilir. Bu belirteci takiben yazılacak sayı veya sayıya çözümlenebilir ifade, yukarıdaki özel karakterler ile belirtilmeyen tüm sayısal ifadeler için taban teşkil eder.

 

.radix    16

 

Ancak burada bir çakışma söz konusu olacaktır. Zira 16'lık tabana rağmen 'b' veya 'd' harfleri kullanılmak suretiyle taban ayarlaması yapılmaya çalışıldığında assembler, bu harfleri de 16'lık düzenin bir parçası olarak kabul edecektir. Dolayısıyla, yine assembler tarafından aynı nitelikte kabul edilen 'b' -> 'Y' ve 'd' -> 'T' dönüşümü yapılmalıdır.

 

16'lık düzendeki sayılarda, ilk 10 rakamdan sonraki rakamlar A-F harfleri ile ifade edilir. Ancak değişken adları da bu harfler ile başlayabildiğinden derleyici, A-F rakamları ile başlayan 16'lık sayıların yorumlanışında karışıklık yaşar ve bunu değişken adı olarak kabul etmek ister. Dolayısıyla derleme esnasında hataya yol açılır. Bu hatanın önlenmesi için, 16'lık sayıların da en azından '0' (sıfır) rakamı ile başlatılmasında fayda vardır. Derleyici bunu fazladan hane olarak görmez, sadece karışıklık önlenir.

 

Hatırlanacağı üzere x86 mimarisindeki verilerin doğal sınırlar dahilinde bulunması, işlem başarımını arttırmaktadır. Ancak 1 bayt uzunluğundaki kimi verilerin kullanımı neticesinde bu hizalamanın dışına çıkılabilmekte, takiben kullanılacak dword büyüklüğündeki değişkenler veya veriler ile de bu sorun devam edebilmektedir. Assembler, hizalama işleminin programcı tarafından ele alınabilmesi için align komutunu sunmaktadır. Bu komutu takiben yazılacak sayı ile bir sonraki verinin, o sayı ile tam bölünebilir ilk bellek alanında yer alması sağlanabilir. Eğer içinde bulunulan segment kod niteliği taşımıyorsa, atlanılan alanlar 0 ile, kod özelliği taşıyorsa da NOP (no operation) komutları ile doldurularak süreklilik sağlanır.

 

align    4

 

Bu kullanım, 32 bitlik platformda doğal sınır koşulunu gerçekleştirir. Özel SIMD işlemleri yapılmadığı sürece gerektiği hallerde bu hizalama yeterli olacaktır. 16 bitlik platformlarda hizalamanın koşulu doğal olarak 2'nin katları olacaktır. Bu duruma özel olarak assemblerda, parametre gerekmeksizin bir komut bulunur: even. Bu komut, align 2 ile eş anlamlıdır.

 

Program Kodu

 

Bu bölüm, yapılacak işin esas alındığı ve makine kodunun yazıldığı bölümdür. Oldukça uzun olabilen bu bölümün başlangıcında, başlangıç etiketi olarak adlandırılan bir belirteç bulunabilir. Bu etiket, hem programın çalışmaya başlayacağı noktayı gösterir, hem de kod bölümünün en sonunda, programın bittiğini işaret etmek için kullanılır. Derleyici, bu sayede kod bölümünün başlangıç ve bitişini hedef ikili dosyaya işler. Bu etiketten önceki verilerin programın çalışması üzerinde etkisi yoktur. Dolayısıyla söz konusu ara bölgede genel değişken tanımlaması bile yapılabilir.

 

Kod bölümünde yazılan her bilgi, sıra ile derlenir ve aynı sırada kod oluşturulur. Dolayısıyla bölüm başlangıcında serbest olarak yazılabilecek ASM rutinlerinin ardından, evvelinde herhangi bir dallanma komutu kullanılmadan yazılacak bir alt programa doğrudan ve denetimsiz giriş yapılacak, alt programın dönüş komutu ile de programdan denetimsizce çıkılabilecek ve hatta kimi durumlarda sistem kararlılığı bozulabilecektir. Dolayısıyla, hatalı sırada kod yazarak denetimsiz girişlerden kaçınılmalıdır.

 

Aşağıda en basit çalıştırılabilir kodun daha açık şekli yer almaktadır:

 

; Cihan Atıl Namlı 32 bit Assembler Tutorial
; Minimal requirements example

 

.386   ; Target platform, forces 32-bit code.
.model flat, stdcall ; Memory model and calling convention.

 

.code   ; Code segment start

 

start:   ; Start label, used later for closing code segment.

 

ret   ; Return to system instruction

 

end start  ; Code segment close down, end of file.

 

Bu kod, yazının başındaki ile aynı kod olup, her satırın yanında açıklamalar mevcuttur. Görüleceği üzere kod bölümünün başlangıcında kullanılan etiket, dosyanın sonunda yeniden kullanılmış ve dosyanın bitimi işaret edilmiştir. Aşağıdaki ikinci örnek, ön değerli değişkenlerin tanımlanarak program içerisinde kullanılmasını göstermektedir.

 

; Cihan Atıl Namlı 32 bit Assembler Tutorial
; Declaration of initialized Variables example

 

.386
.model flat, stdcall

 

.data   ; Starting data segment.

 

byte_variable db 15   ; Value is treated as decimal by default.
word_variable dw 1101000100011001b ; 16 bits binary word.
dword_variable dd 0A1B234H  ; Remark: Because of identifier-like treating of hexadecimal numbers which start with letters, a '0' must be appended to beginning.

 

.code    ; Starting code segment, while closing data segment.

 

start:

 

mov al, byte_variable  ; Legal instruction.
mov ax, word_variable  ; Legal instruction.
mov eax, dword_variable  ; Legal instruction.

 

ret

 

end start

 

Yukarıdaki örnekte, tanımlanmış ve ön değerleri verilmiş olan genel değişkenler, eax yazmacının parçaları ile kullanılmıştır. Yukarıdaki kullanımda, farklı boyutlardaki parametrelerin tek komut ile işlenmeye çalışılması hata verecektir.

 

Aşağıdaki örnekte, ön değer verilmemiş değişkenlerin tanımlanması ve kod içerisinde değişkenlere değer ataması gösterilmiştir.

 

; Cihan Atıl Namlı 32 bit Assembler Tutorial
; Declaration of uninitialized variables example

 

.386
.model flat, stdcall

 

.data

 

byte_variable db 15   ; These variables are 7 bytes long at total. But
word_variable dw 1101000100011001b ; they will grow the code by 512 bytes.
dword_variable dd 0A1B234H

 

.data?

 

uninitialized_byte db ?  ; These variables are declared but don't affect code size.
uninitialized_word dw ?
uninitialized_dword dd ?

 

Text_buffer  db 256 dup (?) ; Macro that duplicates a value as requested.


.code

 

start:

 

mov al, uninitialized_byte ; Legal instruction, but result is unpredictable.
mov ax, uninitialized_word ; Legal instruction, but same result as above.
mov eax, uninitialized_dword ; Legal instruction, but same result as above.

 

mov uninitialized_byte, 10  ; Legal assignment for 386 and successors
mov uninitialized_word, 10000  ; Legal assignment for 386 and successors
mov uninitialized_dword, 100000  ; Legal assignment for 386 and successors

 

ret

 

end start

 

Dikkat edilecek olunursa, ön değer atanmamış değişkenlerin bellekte bulundukları bölgeler herhangi bir temizleme işlemine maruz olmamaktadır.Dolayısıyla bu değişkenlerin başlangıç değerlerini kestirmek mümkün değildir. Ayrıca 386 değin mümkün olmayan, belleğe doğrudan değer yazma işlemine yer verilmiştir.

 

Aşağıdaki örnek, alt programların prototip ve gövde tanımlamasını, kod içerisinde çağırılmasını ve geri değer döndürme standardını göstermektedir. Kod içerisindeki yorum satırlarının gösterilişine dikkat edilmelidir, zira bilhassa prototip tanımlamalarında kullanılan (:) operatörü ile (;) yorum operatörü birbirine oldukça benzemektedir ve karıştırılmamalıdır.

 

; Cihan Atıl Namlı 32 bit Assembler Tutorial
; Subroutine examples

 

.386
.model flat, stdcall

 

; Subroutine(procedure, function) prototypes.

 

Subroutine1  PROTO    ; No parameter.
Subroutine2  PROTO: DWORD   ; 1 DWORD
Subroutine3  PROTO: SDWORD, : REAL4  ; 2 DWORD
Subroutine4  PROTO: DWORD, : DWORD, : WORD ; 2 DWORD, 1 WORD
Subroutine5  PROTO: WORD, : SWORD, : WORD ; 3 WORD


.data

 

WordVar  dw  40000
DwordVar dd  555555
SwordVar SWORD  -7868
SdwordVar SDWORD  -2000000
FloatVar REAL4  375.857

 

.data?

 

RetVal  DWORD  ?  ; Subroutine return parameter, included only for example.

 

.code

 

start:

 

call Subroutine1 ; Legal

 

push DwordVar
call Subroutine2 ; Legal
add esp, 4  ; Stack cleanup (stdcall style)..

 

push FloatVar
push SdwordVar
call Subroutine3 ; Legal, parameters pushed in order right from left.
add esp, 8  ; Stack cleanup.

 

push WordVar
push DwordVar
push DwordVar
call Subroutine4 ; Legal. Assembler will automatically specify WordVar size.
add esp, 10

 

push WordVar
push SwordVar
push WordVar
call Subroutine5 ; Legal.
add esp, 6

 

;--------------------------------------------
; MASM Macro Syntax for Calling Subroutines ;
;--------------------------------------------

 

; This style includes parameter count and type checking. It speeds up coding.
; Also, because of the language type, the stack is automatically cleaned up.

 

invoke Subroutine1

 

invoke Subroutine2, DwordVar

 

invoke Subroutine3, SdwordVar, FloatVar

 

invoke Subroutine4, DwordVar, DwordVar, WordVar

 

invoke Subroutine5, WordVar, SwordVar, WordVar

 

jmp endlabel

 

;-------------------------------------------------------------------------------

 

Subroutine1 proc

 

mov eax, RetVal
ret

 

Subroutine1 endp

 

;-------------------------------------------------------------------------------

 

Subroutine2 proc Parameter1: DWORD

 

mov eax, RetVal
ret

 

Subroutine2 endp

 

;-------------------------------------------------------------------------------

 

Subroutine3 proc Parameter1: SDWORD, Parameter2: REAL4

 

mov eax, RetVal
ret

 

Subroutine3 endp

 

;-------------------------------------------------------------------------------

 

Subroutine4 proc Parameter1: DWORD, Parameter2: DWORD, Parameter3: WORD

 

mov eax, RetVal
ret

 

Subroutine4 endp

 

;-------------------------------------------------------------------------------

 

Subroutine5 proc Parameter1: WORD, Parameter2: SWORD, Parameter3: WORD

 

mov eax, RetVal
ret

 

Subroutine5 endp

 

;-------------------------------------------------------------------------------

 

endlabel:

 

ret

 

end start

 

Görülebileceği üzere alt programlar, sadece farklarını gösterebilmek amacıyla hem klasik yöntemle, hem de MASM'nin sunduğu invoke komutu ile çağırılmıştır. Her iki çağrı yöntemi de geçerli olup, invoke komutunun sağladığı hata denetimi sayesinde daha güvenli bir yöntem izlenebilmektedir. Ayrıca alt program gövdelerinin tanımlanmaya başlanmasından hemen önce kullanılan jmp komutu ile kodun en sonuna gidilmiştir. Aksi takdirde Subroutine1 alt programının içerisine denetimsiz olarak girilecek ve istenmeyen bir işlem yapılabilecektir.

 

Aşağıdaki kod, yukarıdakilerden farklı olarak ortaya işe yarar veriler çıkarmaktadır. Bu kod, programlamada bir teknik olan "lookup table" yöntemi ile, sıklıkla kullanılması söz konusu olabilecek iki haneli sayıların karelerini hesaplayarak bir diziye yazmaktadır. Programcı, daha sonra isterse bu dizideki değerleri kullanarak işlem yükünden kurtulabilir.

 

; Cihan Atıl Namlı 32 bit Assembler Tutorial
; Lookup table preparing example

 

; Lookup table for squares

 

.386   ; Target platform, forces 32-bit code.
.model flat, stdcall ; Memory model and calling convention.

 

.data?

 

Squares  dw 100 dup (?)  ; Preparing memory for squares of integer numbers from 0 to 99.

 

.code

 

start:

 

xor ecx, ecx   ; Assure ECX = 0.

 

loopstart:

 

mov eax, ecx   ; Copy source number to EAX.
mul eax    ; Get square of EAX.
mov Squares[ecx*2], ax  ; Move the result to the array.
inc ecx    ; Increment number.
cmp ecx, 100   ; Compare if it is reached to 100.
jb loopstart   ; If not, process the loop for next number.

 

ret    ; Return to system/caller.

 

end start

 

Yukarıdaki kod, istendiğinde bir alt programa da dönüştürülerek, daha kapsamlı bir uygulama içerisinde modül olarak kullanılabilir. Bu durumda yapılması gereken, oluşturulacak alt program için parametre tanımlaması yapmaktır. Squares dizisinin adresinin verilmesi ve istenen değer aralığının belirtilmesi, işin yapılması için yeterli olabilir.

 

Aşağıdaki kod, şimdiye değin işlenen pek çok bilginin kullanıldığı ve yeni bilgilerin yer aldığı bir programdır. Bu program, 0'dan 360'a dek tüm tamsayı açı değerlerinin sinüsünü hesaplar; sıklıkla kullandığımız açıların sinüs değerlerini de küçük bir ileti penceresinde gösterir. Hesaplamalar için x87 FPU'nun imkanlarından faydalanılmıştır. Alternatif bir yöntem olarak Taylor serilerinden de faydalanılabilirdi. Bu pencerenin kullanılması ve iletinin gösterilmesi için Windows'un çeşitli hizmetlerinden de faydalanılmıştır.

 

; Cihan Atıl Namlı 32 bit Assembler Tutorial
; FPU example

 

; Lookup table for sines

 


.386   ; Target platform, forces 32-bit code.
.model flat, stdcall ; Memory model and calling convention.

 

includelib "D:\Microsoft Visual Studio\VC98\Lib\msvcrt.lib"
includelib "d:\masm32\lib\user32.lib"

 

sprintf  PROTO C :DWORD,:VARARG
MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD

 

.data?

 

SineTable REAL8 360 dup (?)  ; Preparing memory for sines of integer numbers from 0 to 359.
i  dd ?
n180  dd ?
Text  db 1024 dup (?)

 

.code
string  db "Sin(15) = %f, Sin(30) = %f, Sin(45) = %f",10,"Sin(60) = %f, Sin(75) = %f, Sin(90) = %f",0
progtitle  db "Sine Table Example",0

 

start:

 

mov i, 0   ; i = 0
mov n180, 180   ; n180 = 180
fldpi    ; Load pi onto FPU stack.

 

loopstart:   ; Start of process phase.

 

fild i    ; Load number onto FPU stack.
fmul st(0), st(1)  ; Multiply number with pi.
fidiv n180   ; Divide result to 180.
fsin    ; Calculate sine of the number st(0).
mov eax, i   ; EAX = i
fstp SineTable[eax*8]  ; Copy calculated number to the array.
inc i    ; Increment number.
cmp i, 360   ; Compare number with 360.
jb loopstart   ; If not reached, process the loop for next number.

 

xor eax, eax

 

invoke sprintf, ADDR Text, ADDR string, SineTable[15*8], SineTable[30*8], SineTable[45*8], SineTable[60*8], SineTable[75*8], SineTable[90*8]
invoke MessageBoxA, 0, ADDR Text, ADDR progtitle, 0

 

ret    ; Return to system/caller.

 

end start

 

Yukarıdaki yeni bilgilere değinmekte fayda vardır. Programımız içinde kullanılan MessageBoxA ve sprintf işlevleri, sırasıyla user32.dll ve msvcrt.dll devingen bağlantı kütüphanelerine statik olarak bağlanılarak kullanılmıştır. Dolayısıyla, söz konusu DLL'ler içerisinde bu işlevlerin adreslerine .lib uzantılı ve aynı adlı statik bağlantı kütüphaneleri kullanılarak erişilmiştir. Daha sonra ise, invoke komutu ile kullanılabilmeleri için prototip tanımlamaları, söz konusu işlevlerin MSDN'deki dokümantasyonlarına uygun olarak yapılmıştır. Programın geri kalan kısmında ise tamamen işin yapılmasına yönelik FPU işlemleri ve döngüler söz konusudur. Yukarıdaki kod MASM tarafından derlendiğinde hata vermeyecek ve 1536 baytlık bir EXE dosyası üretecektir. Aynı işi yapmaya yönelik bir C/C++ programının üreteceği kodun boyutu, yukarıdakine göre oldukça büyüktür.

 

Assembler, bilhassa ondalık sayı işlemlerinin yapılmasında çok etkilidir. Çünkü kimi karışık işlemler ve dönüşümler, yüksek seviyeli dil derleyicileri tarafından oldukça uzun ve gereksiz işlemler ile gerçekleştirilmeye çalışılır. Çünkü FPU işlemlerinin yapılabileceği toplam bölge çok kısıtlıdır (8 eleman) ve işlemler birbirini kolayca etkileyebilmektedir. Yukarıdaki işlevi gerçekleştirecek bir C/C++ kodu derlenir ve hata ayıklama yöntemi ile incelenirse, sistemin kararlılığını korumaya yönelik pek çok, ancak gerekli olmayan komuta rastlanır. Bunun sebebi, derleyicinin ne yapmak istediğinizi tam olarak bilemeyeceği gerçeğidir. Assembler'in en büyük üstünlüğü buradadır. Siz istemeden kolay kolay kod oluşturulmaz. Sadece yerel değişkenler ve alt programlar için bellek ayırma amacıyla yığın hizalamaya yönelik ekstra 1-2 işleme rastlanır.

 

Assembler ile yapılabilecek en verimli işlem, kod optimizasyonudur. Yüksek seviyeli dil derleyicileri, mümkün olduğunda optimizasyon yapmaya çalışırlar. Ancak eğer bir kod, çok yüksek öncelik ile (neredeyse sistemde tek başınaymış gibi) çok sık çalışacak ve zamana karşı yarışacaksa, kod içeriğinin son derece hızlı ve sadece amaca yönelik bir yapı taşıması gerekir. Bu durumda, ya programın tamamı assembler'da yazılmalı, ya da kodun sadece o bölümü satır içi assembler kullanılarak yazılmalıdır. En güvenli ve hızlı yöntem ise, söz konusu kritik bölümün assembler'da yazılarak bir kütüphane haline getirilmesidir. Bu sayede statik bağlantı ile yüksek seviyedeki dilden fonksiyon çağırılabilir. Aşağıdaki program, Pentium ve sonrası işlemcilerin çalışma hızlarını ölçmeye yardımcı olmaktadır.

 

.686p
.model flat, stdcall
option casemap: none

 

include d:\masm32\include\windows.inc
include d:\masm32\include\user32.inc
include d:\masm32\include\kernel32.inc

 

includelib d:\masm32\lib\user32.lib
includelib d:\masm32\lib\kernel32.lib

 

      szText MACRO Name, Text:VARARG ; This macro creates a string and returns its address.
        LOCAL lbl
          jmp lbl
            Name db Text,0
          lbl:
        ENDM

 

.data?

 

Frequency dq ?
Counter1 dq ?
Counter2 dq ?
Difference dq ?
Time  dq ?
nval  dd ?
Speed  dq ?
Text  db 256 dup (?)

 

.code

 

start:

 

szText Cpuspeed, "Cpu Speed"

 

invoke GetModuleHandle, NULL
invoke SetPriorityClass, eax, REALTIME_PRIORITY_CLASS
invoke GetCurrentThread
invoke SetThreadPriority, eax, THREAD_PRIORITY_TIME_CRITICAL

 

invoke QueryPerformanceFrequency, offset Frequency

 

.if eax==0
szText t2, "This system doesn't support a high performance counter. Application will now quit."
invoke MessageBox, NULL, ADDR t2, ADDR Cpuspeed, NULL
jmp quit
.endif

 

szText starttext, "ECX register will be decreased 2^32 times using loop method. Click OK to start.",10,"For most reliable results, please avoid user inputs, even mouse motions."
invoke MessageBox, NULL, ADDR starttext, ADDR Cpuspeed, NULL

 

invoke QueryPerformanceCounter, offset Counter1
xor ecx, ecx
@@: loop @B
invoke QueryPerformanceCounter, offset Counter2

 

mov eax, dword ptr Counter2
mov ecx, dword ptr Counter2 + 4
sub eax, dword ptr Counter1
sbb ecx, dword ptr Counter1 + 4

 

mov dword ptr Difference, eax
mov dword ptr Difference + 4, ecx

 

fild Difference
fild Frequency
fdivp st(1), st(0)
mov nval, 1000000000
fimul nval
fistp Time
fild Time
mov nval, 8
fidiv nval
mov nval, 1000000000
fidiv nval
mov nval, 40000000H
fidiv nval
fld1
fdiv st(0), st(1)
fistp Speed

 

szText endtext, "Performance Counter Frequency: %I64d Hz.",10,"Counted Ticks: %I64d",10,10,"Time Passed: %I64d ns.",10,10,"Cpu Speed: %I64d Hz."
invoke wsprintf, ADDR Text, ADDR endtext, Frequency, Difference, Time, Speed

 

invoke MessageBox, NULL, ADDR Text, ADDR Cpuspeed, NULL

 

quit:

 

invoke ExitProcess, 0

 

end start

 

Bu program, öncelikle çoklu görev ortamındaki kendi önceliğini mümkün olan en yüksek seviyeye çıkarmakta, daha sonra da ECX yazmacını 2^32 kere döngü içerisinde azaltarak aradaki süre farkını ölçmektedir. Bu süre farkının ölçümü için Windows tarafından sunulan iki adet işlev kullanılmakta; söz konusu işlevler doğrudan donanıma erişerek 8253/8254 PIT denetleyicinin kanallarını sorgulamaktadır. Bu öğe, standart bilgisayar donanımının mecburi bir öğesi olup 3 kanala sahiptir. Bu kanallardan biri DRAM'in tazelenmesi suretiyle bilginin korunmasını, bir diğeri ise PC speaker'in ses vermesini sağlar. Sonuncusu da Windows tarafından yüksek başarımlı sayaç (high performance counter) olarak kullanılır.

 

Yukarıdaki işi yapan bir programın yüksek seviyeli bir dil ile aynı sonucu elde edecek biçimde yazılması son derece güçtür.

 

Döngü ve Şartlı İfade Blokları için Kolaylıklar

 

Yukarıdaki örnekte dikkat çekebilecek bir diğer unsur ise, şartlı ifadelerin denetimi için kullanılan bloklardır. MASM'da .if, .elseif, .endif, .while, .endw, .repeat, .until, .untilcxz gibi döngü ve şartlı ifade komutlarını üretebilecek iç makrolar bulunur. Bu sayede döngülerin üretilmesinde adı sanı karışacak pek çok etiketin kullanılmasından kurtulmuş olunur.

 

Bu komutların kullanımındaki taslaklar şu biçimdedir:

 

.if    şartlı ifade

    assembler ifadesi

    ...

[.elseif    şartlı ifade

    assembler ifadesi

    ...]

.endif

 

.while    şartlı ifade

    assembler ifadesi

    ...

    [.continue [.if şartlı ifade]]

    [.break [.if şartlı ifade]]

    ...

.endw

 

.repeat

    assembler ifadesi

    ...

    [.continue [.if şartlı ifade]]

    [.break [.if şartlı ifade]]

    ...

.until    şartlı ifade    |    .untilcxz    [şartlı ifade]

 

Şartlı ifade kısımlarında C/C++'ta geçerli olan herhangi karşılaştırma operatörü kullanılabilir. Operatörün her iki tarafındaki elemanlar, assembler'daki cmp komutuna uygun olacak operandlar olmalıdır. Kolaylık sağlayan bu komutların arasında for komutunun bulunmayışı ilginç gözükse de, while komutunun doğru kullanımı ile aynı kapıya çıkılabilecektir.

 

Alt Programlarda Yerel Değişken Tanımlamaları

 

Yerel değişkenler, ön değer verilmediği sürece çalıştırılabilir dosya boyutunu etkilemeyen ve sadece alt program içerisinde dahili olarak erişilebilen bellek bölgeleridir. Assembler, yerel değişken tanımlamalarında yüksek seviyeli diller ile aynı yöntemi kullanır, yani yığın işaretçisinin altında kalan bölgeyi bu amaçla değerlendirir. Yerel değişken tanımlamasına ilişkin taslak ve örnekler aşağıdaki gibidir.

 

local    değişken [[sayı]] [: veri türü] [, değişken [[sayı]] [: veri türü]] ...

 

local deyimi ile bir satırda birden fazla değişken tanımlaması mümkün olmaktadır. Ancak yine de farklı türdeki değişkenlerin farklı satırlarda yer alması, anlaşılırlık açısından faydalı olacaktır.

 

WndProc    proc    hwnd :DWORD, uMsg :DWORD, wParam :DWORD, lParam :DWORD

 

    LOCAL var    :DWORD
    LOCAL caW    :DWORD
    LOCAL caH    :DWORD
    LOCAL Rct    :RECT
    LOCAL buffer1[128]: BYTE

    LOCAL buffer2[128]: DWORD
    LOCAL szDropFileName[260]: BYTE
...

...

ret

 

WndProc    endp

 

Yukarıdaki örnekte yerel değişken tanımlaması açıkça görülmektedir. Tanımlanan değişkenlerden biri bir yapıdır. MASM, yerel değişkenlere tanımlandıkları yerde ön değer verilmesini desteklememektedir. Ancak şunun da iyi bilinmesi gerekir ki söz konusu ön değer tanımlaması desteklense bile bu işlem, assembler dilindeki mov komutları ile gerçekleştirilmek zorundadır. Çünkü yerel değişkenlerin mutlak yeri yoktur, ESP'ye göre göreceli konumları söz konusudur.

 

Yerel değişken erişimi, bilinebileceği üzere EBP ile yapılır. Assembler, tanımlanan yerel değişkenlerin toplam boyutunu hesap eder ve ilk hamlede ESP'yi bu değer kadar aşağı çeker. Böylece yığın işlemleri ile yerel değişken erişimleri birbirinden yalıtılmış olur. Daha sonra sadece EBP'nin ayarlanması ile ara bölgedeki belleğe erişilir ve işlemler yapılır.

 

Kimi zaman alt programlar veya çeşitli işlemler, parametre olarak değişkenin adresini talep edebilir. Bu teknik, değişkenlerin global veya ortak kullanımında son derece gereklidir. Ayrıca bir işlevin birden fazla değer döndürmek zorunda olması durumunda da parametre olarak döndüreceği değerlere ilişkin adresler istemesi doğaldır. Genel değişken olarak tanımlanan bir ifadenin bellek üzerindeki konumu sabittir ve değişkenin önüne offset deyimi yerleştirilmek suretiyle bu sabit ifadeye erişilebilir(bu sabitlik RVA bazındadır, ancak işletim sistemi tüm RVA bloklarını doğrusal olarak işlediğinden konum sabit olacaktır). Ancak yerel olarak tanımlanan bir değişken için bu şekilde bir sabitlik söz konusu değildir. Çünkü yerel değişkenler, değişken değere sahip olan ESP'ye göre göreceli bir konumda tanımlanmaktadır. Eğer herhangi bir işleve bu şekilde tanımlanmış bir değişkenin adresi aktarılacağında offset deyimi hata verecektir, zira bu deyim sabit bir sayıya çözümlemeyi hedefler. Bu durumda yerel değişkenin adresinin aktarımı için ADDR deyimi kullanılmalıdır. ADDR deyimi, ilgili yerel değişkenin mutlak adresine bir assembler komutu olan lea ile ulaşır ve ilgili ifadeye aktarır. Bu konuyu bir örnekle açıklamakta fayda görüyorum.

 

WinMain proc hInstance: DWORD, hPrevInstance: DWORD, CmdLine: DWORD, CmdShow: DWORD

 

LOCAL wc: WNDCLASS
LOCAL msg: MSG
...

...

    StartLoop:
      invoke GetMessage,ADDR msg,NULL,0,0
      cmp eax, 0
      je ExitLoop
      invoke TranslateMessage, ADDR msg
      invoke DispatchMessage, ADDR msg
      jmp StartLoop
    ExitLoop:

 

      return msg.wParam

 

WinMain endp

 

Yukarıdaki kod parçasında GetMessage, TranslateMessage ve DispatchMessage alt programları, parametre olarak msg yerel değişkeninin adresini almaktadır. Burada ADDR yerine offset kullanılması hata ile sonuçlanacaktır, çünkü msg değişkeninin yeri sabit değildir. ADDR deyimi, ifadeye yeni bir kod ekleyerek amaçlanan işlemi gerçekleştirecektir. Aşağıda, ifadenin assembler tarafından çözümlenmiş hali görülmektedir.

 

...

...

StartLoop:

   push 0
   push 0
   push 0
   lea eax, [ebp-44h]
   push eax
   call [email protected]
   cmp eax, 0
   je ExitLoop
   lea eax, [ebp-44h]
   push eax
   call [email protected]
   lea eax, [ebp-44h]
   push eax
   call [email protected]
   jmp StartLoop
ExitLoop:

 

mov eax, [ebp-3Ch]

ret

...

...

 

Görüldüğü gibi msg değişkenine ait taban + sabit biçimindeki adres, lea komutunda parametre olarak kullanılmış ve eax yazmacına açık adresin yazılması sağlanmıştır. Ardından assembler, eax yazmacını parametre olarak yığına göndermiştir. Assembler, aynı işlemi 3 kere tekrarlamış ve bu başarım kaybına neden olmuştur. Assembler'in bu gibi kompleks işlemlerde en iyisini yapmadığının bilindiği durumlarda kodun programcı tarafından düzenlenmesinde fayda vardır. İşlemin 3 kere tekrarlanmasındaki gerekçe, her alt program çağrısından sonra eax yazmacının eski değerini yitirecek olmasıdır. Hatırlanacağı üzere Windows ortamında programlama yapılırken genel amaçlı yazmaçlardan EAX, ECX ve EDX'in değerleri, alt programlar tarafından korunmamaktadır.

 

Alt Programlarda Korumalı Yazmaçların Kullanımı

 

Windows ortamındaki programlarda EBX, ESI ve EDI yazmaçlarının sabit olması gereği kimi zaman programcıyı yapılacak işlemlerde güç durumda bırakabilir. Öyle ki çok karışık adresleme ve tablo erişimlerinde EAX, ECX ve EDX yazmaçları kimi zaman yetersiz kalabilmektedir. Bu durumda programcının iki seçeneği vardır. Bunlardan biri, korumalı 3 yazmaçtan ihtiyacı olanları önce push komutu ile korumak, sonra işlemler için kullanmak ve en son olarak da pop komutuyla geri çekmektir. Assembler'in bu amaca yönelik olarak sunduğu bir deyim bulunur: uses. Bu deyim, alt program gövde tanımlamasında, parametre tanımından hemen önce yer alır.

 

işlev adı    proc    [uses    yazmaç1 yazmaç2 ...]    [uzaklık]    [işlevin çağrı dili]    [[parametre1] : veri türü] [, [parametre2] : veri türü] [, ...]

 

Bu işlem, alt program girişinde prolog kodun belirtilen yazmaçları yığında saklamasını ve alt program bitiminde epilog kodun söz konusu yazmaçları yığından çekmesini sağlar. Yığında saklanması istenen yazmaçlar birbirinden boşluk ile ayrılarak yazılmalıdır. Bu kullanım, EBX, ESI ve EDI dışında gerekli değildir; zira üst program EAX, ECX ve EDX 'in bozulmadan dönmesini beklememelidir.

 

Yazmaçları korumanın diğer bir yöntemi ise pushad ve popad komutlarıdır. Bu komutlar, EFLAGS da dahil olmak üzere bütün yazmaçları yığında saklar. Ancak optimizasyonun gerekli olduğu noktalarda kullanılmamalıdır; zira işlem uzun sürmekte ve fazlaca gereksiz bellek harcamaktadır.

 

Özel Operatörler ve Deyimler

 

Yüksek seviyeli dillerde olduğu gibi MASM'da da özel amaçlı operatörler bulunur. Özel amaçlı operatörlerin çoğu derleme zamanında çalışır ve sabit ifade üretirler.

 

lengthof ve sizeof operatörleri, tanımlanan değişkenler ve değişken dizileri hakkında faydalı bilgiler sunarlar. lengthof operatörü, ardından yazılan dizi ifadesinde kaç eleman bulunduğunu belirtir. Eğer tekil bir tanımlama söz konusuysa doğal olarak 1 değerini döndürecektir. sizeof operatörü ise, bir dizinin bayt cinsinden toplam bellek uzunluğunu belirtir. Tekil tanımlamalarda ise değişkenin bayt cinsinden boyutunu döndürür.

 

dizi    dd    64 dup(?)

degisken    dw    5

 

lengthof dizi -> 64

sizeof dizi -> 64 * 4 = 256

 

lengthof degisken -> 1

sizeof degisken -> 2

 

type operatörü, çok işlevli bir operatördür. Dizi olarak tanımlanmış bir değişken için kullanıldığında dizideki her bir elemanın boyutunu verir. Bir yapı değişkeni için kullanıldığında ise yapının toplam boyutunu verir. Bir sabitin önünde kullanıldığında 0, etiketin önünde kullanıldığında ise kod başlangıcına olan uzaklığı verir. Yazmaçlar ile kullanıldığında da yazmacın boyutunu döndürür.

 

type dizi -> 4

type RECT -> 16

type 5 -> 0

type EAX -> 4

 

org deyimi, kimi zaman komut işaretçisinin (EIP) konumunu belirli bir değere getirmek için kullanılır. Bu işlem genellikle programların başlangıçlarında yapılır ve başlangıç etiketinin konumunu ayarlamış olur. Hatırlanacak olursa COM dosya biçiminde bu başlangıç değeri, ilk 256 baytın PSP(Program Segment Prefix) olmasından ötürü 100h idi. Tiny bellek modeli ile COM dosyası üretileceği zaman DOS ortamı için org 100h deyimini kullanmak gerekir. Deyimden sonra kullanılacak parametre, sayıya çözümlenebilir bir ifade de olabilir.

 

high ve low deyimleri, kelime(word) boyutuna çözümlenebilir bir ifadenin yüksek ve düşük baytlarını 2'ye tümleyen düzeninde döndürür. highword ve lowword deyimleri ise çift kelime(doubleword) boyutuna çözümlenebilir bir ifadenin yüksek ve düşük kelimelerini 2'ye tümleyen düzeninde döndürür. Parametre olarak kullanılacak ifadenin sayısal veya sayısala çözümlenebilir bir deyim olması gerekir.

 

Gerek C/C++'ta, gerekse de MASM'da, tek satır yerine birden fazla satır kullanarak kompleks bir ifade yazılmak istendiğinde, ifadenin bitmemiş olduğu her satırın sonuna ters bölü (\) ekleyerek dikey uzatma sağlanabilir. Yazının başından hatırlanacağı üzere assembler'da ifadeler satır satır işlenmekte, satırın bitimi ifadenin bitimi demektir. Ters bölü işareti, satır sonu iminin yok sayılmasını sağlar.

 

Sabit veya sabite çözümlenebilir tamsayı ifadeler üzerinde mantıksal veya bit düzeyinde işlemler yapmak için MASM'de and, or, xor, not, shl ve shr operatörleri mevcuttur. Bu operatörlerin kullanımı ile yapılan işlemler neticesinde, varsayılan ifade büyüklüğüne uygun sabit tamsayı ifadeler üretilir. Bu operatörlerden ilk dördü mantıksal, son ikisi ise bit düzeyinde çalışır.

 

ifade1    and    ifade2

ifade1    or    ifade2

ifade1    xor    ifade2

not    ifade

ifade    shl    basamak

ifade    shr    basamak

 

MASM'de sabit dizeler üzerinde işlem yapmak için catstr, substr, sizestr ve instr operatörleri bulunur. Bu operatörler sayesinde dizeler birleştirilebilir, içlerinde arama yapılabilir, belli kısımlarına ulaşılabilir ve boyutları elde edilebilir. Bu operatörlerin kullanım ayrıntılarına, MASM ile birlikte gelen dokümantasyon aracılığıyla erişilebilir.

 

32 bit assembler için bu noktadan sonrası, tamamen tecrübe ve araştırmaya dayanmaktadır. Programların yazımı için ihtiyaç olunacak teorik ve pratik bilginin %99'unu bu bölümün tamamı ile vermeye çalıştım. Assembler ile yapılacak kodlamanın daha ileri düzeyi OOP(Object Oriented Programming - Nesneye Yönelik Programlama) ve Win32 COM(Component Object Model)'dir. Bu konular, kendi çaplarında özel teknikler olduğundan bu bölümde yer vermedim. Ancak bu bölüm dahilinde verilen bilgiler ile pek çok çeşit Windows uygulaması yazılabilir. Ancak Windows API ile aşina olunması da gerekir.

 

MASM en iyi assembler derleyicisi değildir, ancak TASM veya benzeri assembler programlarını kullananlar için kullanımı aşinadır. MASM, en son MMX teknolojisini destekleyen komutları içermiş ve Microsoft tarafından geliştirilmesine son verilmiştir. Hali hazır en son çalıştırılabilir sürüm 6.14'tür. Daha gelişmiş komutların kullanılabildiği ve benim de kimi zaman kullandığım başka bir assembler Flat Assembler(FASMW)'dir. Hem konsol, hem de GUI sürümünü barındıran tek bir arşiv olan FASMW http://flatassembler.net/ sitesinden indirilebilir. Bu assembler'in aynı zamanda kaynak kodu açıktır.

 

Bu bölümün de sonuna geldik. Bu bölümün 3 parçası ile birlikte yazımı çok uzun sürdü ve epey zahmetli oldu. Bu yazılar belge niteliği taşıdığı için bilgi içeriklerinde hata yapmamaya çalıştım ama yine de gözümden kaçan yerler için sizden özür dilerim. Bölüm dahilindeki tüm örnek ve yorumlar tamamen şahsımın ürünü olup, yazım esnasında faydalandığım kaynaklar şunlardır:

Her türlü soru ve eleştirilerinize açığım. İlgilenenler olursa bu konuda birkaç seanstan oluşan bir seminer de verebilirim.

 

Saygılarımla

 

Cihan Atıl Namlı

 

9 Aralık 2005 Cuma