Alt Programlar

 

Assembler'daki en büyük avantaj, kod bölümü içerisinde alt programın herhangi bir yerde tanımlanabilme özgürlüğüdür. .code deyiminden itibaren hem standart giriş komutlarını yazabilir, hem de istediğiniz yerde kullanıcı işlevlerini, yani alt programları tanımlayabilirsiniz. Aşağıda bununla ilgili basit bir örnek veriyorum:

 

.286

.model small

.stack 100h

.data

 

dizi    dw    0, 1, 2, 3, 4, 5

 

.code

 

start:                                           ; Programı bitirirken kullanmak üzere amaçsız etiket

                                                  ; Kod başlıyor...

mov bx, offset dizi                    ; Daha önce bahsedildiği gibi, çok elemanlı bir dizinin başlangıç adresi elde ediliyor. Bunu derleyici kendisi belirler.

mov cx, 6                                  ; Dizinin eleman sayısı

push cx                                     ; Parametreler aktarılıyor...

push bx                                     ; " "

call dizi_kare                            ; Kullanıcı işlevi çağırılıyor.

add sp, 4                                   ; Standart giriş komutlarının sonu...

 

dizi_kare    proc    near            ; Kullanıcı işlevinin başlangıç/tanımlama noktası

 

push bp

mov bp, sp

xor si, si

dongu:

    mov cx, [bp+6]

    mov si, [bp+4]

    mov ax, [si]

    mul ax

    mov [si], ax

    inc si

loop dongu

pop bp

 

dizi_kare    endp                    ; Kullanıcı işlevinin bitiş noktası

 

end start                                ; Programın başlangıç noktası

 

Bu program, genel değişken olarak tanımlanan dizideki elemanların karelerini alır ve yeniden yerlerine yazar. Şimdi bu programın önemli yerlerini irdeleyelim. İlk olarak platformu, sonra da bellek modelini tanımladık. Sonra yığın için güvenli olacak bir boyut bildirdik. Genel değişkenimizi(diziyi) de tanımladıktan sonra programı başlattık. İlk 6 satırlık kod bölümü, standart giriş komutlarıdır. Bu komutları, C'de "main" işlevi içerisinde yazdığımız ibareler olarak düşünün. Sonra bu kodların içerisinde alt program çağırılıyor. İşte burası çok önemli: Alt programın herhangi bir yerde tanımlanabilme özgürlüğü sayesinde bunu daha aşağılarda, işimiz bittikten sonra tanımlayıp içeriğini yazıyoruz. Alt programların, yani kullanıcı işlevlerinin adlarını verirken değişken adlarını vermedekiyle aynı kurallara sadık kalmamız gerekir. Bir alt programın başlatılma şekli program_adı proc mesafe şeklindedir. Proc kelimesi tamamen sabit olmakla beraber, mesafeden kasıt, bu işlevin kod bölümündeki yerleşimi esnasında CS'nin değişip değişememe ihtimali, yani genel olarak kodun 64k'yı aşarak IP'nin aşılması ihtimalidir. Eğer yazdığınız kod kısa ise bu komutu near olarak, uzun (birkaç bin satırın üzerinde) olduğunu düşünüyorsanız far olarak kullanabilirsiniz. Ancak unutmamanız gereken bir nokta var: Assembler dosyasının başlangıcında tanımlamış olduğunuz bellek modeli, bu çağırım şekillerinde kısıtlayıcı rol oynar. Zira kodun 64k'ya sınırlı olduğu tiny, small ve compact bellek modellerinde far ibaresini kullanmanız hata ile sonuçlanır. Ayrıca near ve far çağırım biçimlerinin işleyişlerini de iyi bilmeniz ve parametrelere doğru biçimde erişmeniz gerekir. Zira call near 032a ifadesinin mikroişlemci içindeki açılımı şuna benzer:

 

PUSH IP

JMP 032A

 

Yığına sadece 2 baytlık bir değer basılmıştır. Ancak call far 1274:8a7c ifadesinin iç açılımına bakarsak:

 

PUSH IP

PUSH CS

JMP 1274:8A7C

 

biçiminde bir yapı ile karşılaşırız ki burada yığına 4 bayt basılmıştır. Bu fazlalık, bizim parametrelere erişim şeklini değiştirmemize neden olur. Bu çok önemli konuyu aşağıda ayrıntılarıyla anlatmaya çalışacağım.

 

Parametreler, işlevi çağıran rutin tarafından yığına atılır. İşlev ise bu parametrelere yığın üzerinde serbestçe çalışabilen işaretçi olan bp(base pointer) ile erişir. Bir işlev çağırılırken ana rutinin parametreleri yığına atmasının ardından call komutu tarafından IP ve hatta gerekirse CS de yığına atılır. Artık sp(stack pointer) parametreleri göstermemektedir, değeri near call işleminde 2, far call işleminde ise 4 eksilmiştir. Bu nedenle önce yığın üzerinde serbest dolaşım için bp'nin sp'ye eşitlenmesi gerekir. Tercihen bp'nin korunumu amacıyla önce bu işaretçi yığına eklenebilir. Özellikle herhangi bir işlevden çıkmadan ikinci bir işlev çağırılıyorsa bu gereklidir. Burada söz edilen işlemler basitçe şu şekilde yapılır:

 

push bp                            ; Bu işlem ile sp 2 eksilmiştir. Zaten sp, daha önce CALL komutu tarafından 2 veya 4 eksiltilmişti.

mov bp, sp                        ; sp'nin 4 veya 6 bayt ötesinde olan parametrelere erişim için bp, sp'ye eşitlenir. Her iki işaretçi de SS üzerinde çalışır.

.

.

.

pop bp                            ; Yığında saklanan bp geri alınır. İşlevin kodu, sp'yi ilk bulduğu gibi bırakmalıdır. Bu işlem ile sp 2 artmıştır.

 

Bp'nin korunmadığı durumlarda, yığına atılan işlev parametrelerine erişirken minimum [bp+2] şeklinde kullanım gerekir. Biz, programımızda bp'yi de (push bp biçiminde) koruduğumuz için ilk parametreye erişirken [bp+4] yazdık. Sonuç olarak artık parametrelere erişirken bp'nin yanındaki sayıya 2 eklememiz gerekmektedir. Parametrelere yanlış biçimde erişmek, kimi zaman programın işlevi adına telafi edilemeyecek hatalar doğurmaktadır. Bu nedenle çok kafanızın karıştığı durumlarda elinize bir kağıt ve bir kalem alarak sp'nin değişimini dikkatle not ediniz. Bu size epey yardımcı olur.

 

Assembler derleyici uygulamaların dosyaları yorumlayabilme ve genel makro ifadelerine duyarlılığı farklı olabilir. TASM, kullanıcı işlevlerine doğrudan parametre aktarımını, yani işlev adının yanına parametreleri yazmayı desteklemez, bunun yerine istediğiniz metoda göre bunu siz yaparsınız. Genel olarak 3 tip parametre aktarım metodu(çağrı biçimi - Calling conventions) standart olarak kabul edilmiştir: stdcall, pascal ve fastcall. Bu metodlardan kısaca söz etmek istiyorum:

  1. Stdcall: C dilindeki ile çok benzer işlev çağırma ve parametre aktarım özelliklerini oluşturur. Dış simgeler ile çalışılırken(mesela .lib uzantılı, genel amaçlı kütüphanelerdeki işlevleri kullanırken) simge önüne alt çizgi (_) eklenir. Prototip işleve ilişkin parametreler sondan başa doğru yığına aktarılır. Yığına atma işlemi sonucunda değişen SP, işlev tarafından bitişte düzeltilir. Çoklu parametre aktarımı mümkündür. En popüler metod olan stdcall, benim de tercih ettiğim metoddur.
  2. Pascal: Pascal dili (ve Borland firması, bu özelliği değiştirmediyse Delphi dili) tarafından kullanılan parametre aktarım yöntemidir. Dış simgeler ile çalışılırken değişken/işlev adında herhangi bir değişiklik yapılmaz. Basic'te de olduğu gibi işlev ve değişken isimleri büyük/küçük harf duyarlı değildir ve parametreler aktarılırken baştan sona doğru yığına atılır. SP'nin düzeltilmesinden işlevi çağıran rutin sorumludur. İşlevlere girişte BP korunur. Çoklu parametre aktarımı mümkün değildir. Bu yöntemin kullanımı çok yaygın değildir. Özellikle Microsoft Visual C++ tarafından artık desteklenmemektedir.
  3. Fastcall: İşlev çağırımlarında adına benzer bir üstünlüğü, yani hızı vardır. Parametrelerin aktarımı için yığın yerine doğrudan doğruya işlemci yazmaçları kullanılır. İlk iki parametre veya küçük parametreler (E)CX ve (E)DX yazmaçları üzerinden aktarılırlar. Eğer daha fazla parametre varsa bunlar, sondan başa doğru yığına atılırlar. SP'nin düzeltilmesinden işlevin kendisi sorumludur. İşlev adının başına at (@) simgesi, sonuna ise yine bir at (@) simgesini takiben parametrelerin bayt cinsinden toplam boyutu onluk düzene göre yazılır. Bu yöntem, Microsoft tarafından ve genellikle 32 bitlik platformda kullanılmaktadır. Parantez içindeki E harfi, IA32 mimarisindeki 32 bitlik yazmaç ön ekidir(32 bitlik IA32 yazmaçları EAX, EBX, ECX, EDX, ESI, EDI, ESP ve EBP'dir. Windows'ta, bir hata meydana gelip de uygulama sonlandırılacağı zaman, ileti penceresinde bu yazmaçların değerleri de belirtilir). 

Sizler, bu metotları harfiyen kullanmak durumunda değilsiniz. Kullanışlılık açısından stdcall ve fastcall metotlarının karışımı olan bir kural belirleyebilirsiniz. Ayrıca her alt programın çağırımının ayrı bir metodu da olabilir. Sonuçta bunlar, üst düzey programlama dilleri tarafından aksi belirtilmedikçe uygulanan kurallardır ve alt düzey program yazarken, eğer program sadece kendi sınırları dahilinde çalışacaksa(yani başka uygulamalarla etkileşimde bulunulmayacaksa) herhangi bir kurala veya standarda bağlı kalma zorunluluğu yoktur. Mesela ben, stdcall kullanmama rağmen, kendi programlarımda parametreleri yazmaçlar üzerinden aktarmayı tercih ederim.

 

Alt programlar veya fonksiyonlar, belirtilen işlemleri yapmalarının yanında geri dönüş değerleri de gönderebilirler. Bu geri dönüş değeri gönderilirken uygulanması gereken kati bir kural olmamakla beraber, 16 bitlik platformlarda 16 bitlik değerler AX yazmacı üzerinden, 32 bitlik değerler ise DX:AX üzerinden iletilir. 32 bitlik platformların tek farkı, bu yazmaçların EAX(32 bit) veya EDX:EAX(64 bit) olmalarıdır. Buradaki kurallar, yine programcının kendisi tarafından belirlenebilir olup, dönüş değer veya değerlerinin boyutuna bağlı olarak bu yazmaçlar özel bir kombinasyonla kullanılabilir. Mesela 4 adet 8 bitlik değer, DH, DL, AH, AL yazmaçlarında ayrı ayrı iletilebilir.

 

Alt Programlarda Yazmaçların Kullanımı

 

Bir alt programın yapacağı iş, büyük ölçüde kendini çağıran rutinden bağımsız olacaktır. Ancak işlemcinin sınırlı sayıdaki yazmaçlarının kimilerinin özel işlevleri vardır ve bazı durumlar için alternatifsiz olabilmektedir. Şimdi bu yazmaçların anlamlarına ve serbestliklerine değinmek istiyorum.

 

80286 işlemcisinin 13 adet 16 bitlik yazmacı bulunmaktadır: AX, BX, CX, DX, SI, DI, BP, SP, DS, ES, SS, CS ve FLAGS yazmaçları. Bu yazmaçlardan FLAGS, bazı karşılaştırma ve aritmetik işlemlerin sonuçlarına göre değişmektedir ve bilgi saklamak amacıyla kullanılamaz. DS, ES, SS ve CS yazmaçları, kod bölümleri belirtmektedir ve ES dışındaki hiçbir bölüm(segment) yazmacı, kullanıcı tarafından değiştirilmemelidir. ES yazmacı da bazı bellek işlemlerinde DI'yi barındırmaktadır ve değiştirilmemelidir. SI ve DI yazmaçları, temel bellek işaretçileri olup tüm aritmetik, mantıksal ve işlevsel komutlarda kullanılabilmelerine rağmen, bellek gösteriminin önemi nedeniyle kullanıcı tarafından işlevlerin içinde temel amacı dışında kullanımından kaçınılması gerekir. BP de SS üzerinde çalışan parametre erişim yazmacıdır ve yine amacı dışında kullanmaktan kaçınmak gerekir. BX yazmacı, SI ve DI'nin tüm esnekliklerini taşır ancak bellek gösteriminde kullanılabilecek tek ana yazmaç olduğundan yine işlevler içerisinde öncelikli olarak kullanılmamalıdır. Sonuç olarak, alt programların içerisinde serbestçe değiştirebileceğimiz 3 yazmaç vardır: AX, CX ve DX. Eğer bu yazmaçlar, yapılacak işler için yeterli değillerse, öteki yazmaçları kullanmaya başlamadan önce güvenlik açısından orijinal değerlerini yığında saklamalı, işlev kodunun bitiminde ise sırasıyla geri çekmeliyiz.

 

Yukarıda belirtilmiş olan kural, 80286 DOS ortamında harfiyen uyulan bir kural değildir. Ancak DOS API(Application Programming Interface)'lerinde hangi işlevin, hangi yazmaç değerlerini bozduğu belirtilir. Bu sayede, bir işlev çağırmadan evvel hangi yazmaçları yığında saklamamız gerektiğini biliriz. Ancak Windows ortamında WINAPI kullanırken tüm işlevler tarafından bu kurala uyulur. Yine tek fark, yazmaçların 32 bitlik olmalarıdır. Merak etmeyin, 32 bitlik ortamlar için de yazı yazacağım...

 

Etiketler

 

Etiketler, bir program içinde dallanma noktalarını belirtmek için kullanılır. Assembler'da etiket belirtimi, üst düzey dillerdeki ile tamamen aynıdır:

 

etiket_adi:

 

Bu belirtimdeki ismin yazım kuralı, değişken tanımlamasındaki ile tamamen aynıdır. Etiketler, herhangi bir bellek değişkeni veya program öğesi değillerdir, derleme esnasına kadar tamamen yer belirtmek amacıyla kullanılmaktadır. Derleme esnasında bu etiketten sonra gelen ilk komutun IP değeri, dallanma komutundaki etiket isminin yerine yazılır. Mesela:

 

.

.

xor si, si

inc di

mov ax, 0864

dongu:

    add ax, bx

jnz dongu

.

.

 

gibi bir kod yazılırken xor si, si satırındaki IP değeri 024a olsun. Satırlarda yapılan işlemlerin boyutlarına göre dongu etiketinden sonra gelen ilk ifade olan add ax, bx ifadesinin IP değeri 024d olacaktır. Derlemenin ardından bu kod şu şekli alacaktır:

 

.

.

xor si, si

inc di

mov ax, 0864

add ax, bx

jnz 024a

.

.

 

Assembler'da etiket kullanmanın herhangi bir inceliği veya kuralı yoktur. Bu nedenle bu konunun üzerinde daha fazla durmaya gerek olmadığını düşünüyorum.

 

TASM ile program yazmak, görüleceği üzere Debug'a göre epey kolaydır. Ancak bunu öğrenmenin zorluğunu da beraberinde getirir. Ayrıca Debug'ta yazılan program, tamamen sizin girmiş olduğunuz kodların bir bütünüdür ve boyutunu siz belirlersiniz. Ancak TASM, .asm dosyasından bir nesne dosyası(.obj) oluşturur. Daha sonra bu nesne dosyası da TLINK tarafından .exe uzantılı programa dönüştürülür. Ortaya çıkan programın boyutu, aynı işi yapan bir .com programına göre daha büyüktür. Bu durum benim pek hoşuma gitmez. Ama yine de ciddi işler ancak .exe programları ile yapılabilir. Bu farkların irdelenmesini bir dahaki bölüme bırakacağım.

 

Bu bölüm ile ilgili yazacaklarım bu kadar. Yine de aklınıza takılan veya anlayamadığınız yerler olabilir. Bu noktaları bana mail yoluyla sorabilirsiniz. Ama eğer ilgileniyorsanız lütfen yazıları baştan sona dikkatle okuyun. Çünkü ben, bunları yazarken bir kitap olmasından çok sizin anlayabileceğiniz şekilde yazmaya gayret ediyorum. Bu arada, yazılarımı doğrudan doğruya Outlook Express üzerinde açtığım "Yeni Posta" penceresinde yazıyorum ve bu yazının formatı yüzünden, siz okurken kimi yerlerin karışabileceğini farkettim. Eğer böyle bir durumla karşılaşırsanız, yazının tamamını seçip bunu Notepad, Wordpad veya Word içine yapıştırabilirsiniz. Bir dahaki bölümde görüşmek üzere...

 

Sakla samanı, gelir zamanı.

 

Cihan Atıl Namlı

 


 

Geçen zamanın özeti:

 

Şu an 17 Ekim 2004 Pazar, saat 23.28 ve CD okuma işlemi sonlarına yaklaşmış durumda: 666MB'ın 664MB'ı kopyalandı. Sanırım gece saat 01.00 gibi bitmiş olur. Sabrımı nasıl buldunuz? Bir işlemin 58 saat sürecek olması ne güzel değil mi? Ya düşünüyorum da, şu an WindowsXP kullanmıyor olsaydım, herhalde yazı yazmayı boşverin, bilgisayara 1 metre yaklaşamazdım bile. Mustafa Fevzi Uysal'ın dediği gibi, yatın kalkın Bill Gates'e dua edin. Adam, malını kaçak kullandığımız halde ses çıkartmıyor. Oysa elalem Opencode olan Linux'u satmaya başladı bile. Sağlık ve sevgiyle kalın...

 

Cihan Atıl Namlı

 

17 Ekim 2004 Pazar, 23.30