Assembly 1
 

Debug ve Kullanımı

İster DOS olsun isterse Windows, çalıştırılabilir iki tipte dosya bulunur. Com veya exe tipindedir. Com tipi dosyalar birebir programın bellekteki şeklinin aynısıdır. Exe tipi dosyalarda ise programın başında işletim sisteminin anlayacağı tipte bir başlık bulunur. İşletim sistemi bu kısımdaki direktiflere göre code, data ve diğer segmentleri bellekte boş olan yerlere göre hafızaya yükler ve ilgili segmentlerin adreslerini registerlere aktararak programin ilk komutu ile işlemi programa bırakır. Com tipi programlarda böyle bir düzenleme yapılmaz, sadece diskten okunur, boş bir bellek adresine yerleştirilerek yürütme programa bırakılır. Tabii dos altında olsa idik tam olarak böyle idi. Windows altında dos için yazılmış olan tüm programlar bir dos emülasyon penceresi altında sanal bir ortamda çalışırlar. (Com dosyaları 64k dan daha uzun olamazlar, exe tipi dosyalarda bu tür bir sınırlama olmayıp gelişmiş özellikler içerirler. Bu nedenlerle exe tip dosyalar daha çok kullanılırlar.)

Exe tipi programlar derlenerek hazırlabilirler, fakat com tipi dosyaların derlenmeye ihtiyacı yoktur ve Debug.exe isimli komut satırı programı ile bunlar programlanabilir. Debug.exe tüm dos ve windows işletim sistemleri ile birlikte geldiği için ek bir program kurmanıza gerek yoktur. Tabii bu programı sadece komutların kullanımlarını görmek, programımızı satır satır çalıştırarak registerleri nasıl etkilediğini görmek amacıyla kullanacağız. Fakat kullandıkça vazgeçmesi zor bir programdır, yani bağımlılık yapar. (Şunu unutmamalıyız: Debug ile 16 bit registerlere ulaşabilir ve bu şekilde program yazabiliriz, 32bit ile çalışmalar bir sonraki bölümden itibaren başlayacaktır.)

Başlat -> Çalıştır yolunu izleyerek command yazıyoruz. Sonra debug yazıp enterleyin. Karşımıza sadece '-' işareti geldi. Artık debug  bizden komut girmemizi bekliyor. Aşağıda ilk programımızı yazdık, şimdi biraz anlatalım.

Komut satırını açarak debug'a geçiş yaptık. 'a100' şeklinde bir ifade var. 'a' komutu bizim assembly kodlarının girişine başlayacağımızı bildirir. sonundaki '100' ise komutları yazmaya başlayacağımız adrestir. Debug ile sadece com tipi dosyalar yazılabiliyor ve bu tür programlar 100h adresinden itibaren yazılırlar. (Debug altında gireceğimiz tüm matematiksel ifadeler hexadecimal sistemdedir!!! Yani 100h aslında decimal 256'ya denk gelir.)

mov ah,2 : MOV komutu bizim en çok kullanacağımız komutlardan biri. Sağdaki register içeriğini veya herhangi bir hexadecimal sabit değerin soldaki registere kaydedilmesidir. Tabii bu durumda ah'ın eski değeri kaybolur, zaten bizim için şu anda bunun önemi yok. (com tipi dosyalarda program ilk çalışmaya başladığında çoğu registerin değeri sıfırdır.)

mov dl,3 : Yukarıdaki MOV komutu gibi burada da bir atama söz konusu. DL isimli 8bit registere 3h değeri kaydediliyor.

int 21, int 20 : Bu komutlar dos kullanıldığı zamanlarda geçerli idi. Benzer işlemde şu anda windows altında API fonksiyonları görev yapmaktadır. Bunları bir tür dos işletim sisteminin bize sunduğu hazır fonksiyonlar olarak görebiliriz. Elbette sadece 20 ve 21 bulunmamakta. Fakat bunlara değinmeyeceğiz. (int=interrupt) Interrupt 20 parametresiz çağırılır ve bizim com tipi programımızı sonlandırır. Interrupt 21 dos ile gelen dev bir fonksiyonlar kütüphanesidir. Hangi numaralı fonksiyonun çağırılacağını ah registerine koymak zorundayız. Fonksiyonun tipine göre istenen başka değerler bulunuyor ise bunlar da ilgili registerlara atanmalıdır. Daha sonra interrup çağrısı yapılır. Biz iki numaralı fonksiyonu (yani ekrana karakter yollama fonksiyonu) çağırıyoruz, bunun için ah a 2 atadık. Daha sonra ASCII karakter tablosuna göre hangi değer ekrana getirilecek ise onun hexadecimal değerini DL registerine atıyoruz. (Burada 3h kalp şeklinde bir karakter, eğer 41h değeri atanırsa 'A' harfi ekrana basılır.) (Bunlardan sonraki satırı ENTER ile boş geçin.)

g : ile programa çalışma emri veriliyor. Gördüğünüz gibi bir karakter ekrana basılıp program int 20 ye geldiğinde normal olarak sonlanıyor.

n : ile programımıza isim veriyoruz. r cx ile cx registerinin değerini değiştirerek 8 giriyoruz çünkü programımızın son satırı 0108 adresini gösteriyor. Ilk satır 0100 ise arada 8 byte yokmu? Yani programımız 8byte uzunluğunda ve biz bunu cx'e aktararak diske kaydedilecek programın boyutunu belirtiyoruz ve w ile işlem tamamlanıyor. Artık q komutunu verip Debug'dan çıkın ve bulunduğunuz dizinde 8 byte uzunluğunda bir kalp.com isimli program var mı bir bakın. Veya hemen kalp yazıp ekran çıktısının debug içinde g komutu ile aldığımızın aynısı olduğunu görebilirsiniz. Bu bizim ilk programımız oldu.

Yukarıdaki çalışma yine aynı program üzerinde yapıldı. Burada t (trace, izle) komutu ile programımızı satır satır çalıştırdık ve registerler üzerindeki etkilerine baktık. Interruptlar birer dev kod bloğu olduğuna göre ve bunları da trace eder isek sayfalar sonra içinde çıkabileceğimiz için bunları pas geçmek için p kullanıldı. Ilk komut işlendikten sonra ekrana ilk gelen bölümü inceleyelim. Ilk komut mov ah,2 idi. Gördüğünüz gibi AX=0200 bölümünde AX in üst 8 bitine yani AH'a 2 değeri atanmış. Burada dikkat ederseniz DS,ES,SS,CS değerleri yani segment registerları aynı blok adresini gösteriyorlar. Yani com tipi dosyalarda kodlar ile datalar aynı yerde bulunuyor. Bu da bize program yazarken dikkatli olmamız gerektiği anlamında önemli bir not. IP pointeri daima sırada işlenecek olan komıtu gösterir. Ilk komut işlendiğine göre 0102 değerini gösteriyor. IP nin hemen sağ tarafındaki ifadeler flagların durumlarını göstermektedir, şu anda önemli değil. Üçüncü satırda sırayla şunlar yer alıyor: CS:IP <MAKINA KODU> <ASSEMBLY KARŞILIĞI>

Ikinci trace'de DL ye 3 değerinin aktarılmış olduğu rahatlıkla görülebilir. Bundan sonraki p komutu ile int 21 işleme girer ve ekranda görülen kalp işareti geliyor. int 20 de programdan çıkılıyor. Bu basit bir örnek oldu, daha karmaşık bir örnek inceleyelim.

mov ah,2 : Interrupt 21 içindeki 2 numaralı fonksiyon yani ekrana karakter basmamız için atıyoruz.

mov cx,FF : Şu ana kadar görmediğimiz döngü işlemi için kullanılacak. FF yani decimal 255 değeri CX e atanıyor. Aşağıdaki loop <adres> komutuna gelindiğinde cx in değeri bir azaltılıyor ve  sıfıra eşit değil ise <adres> değerine atlıyor program. CX içindeki sayı kadar bu loop döngüsü devam ediyor, yani 255 defa. CX sıfıra eşit olduğunda ise hiçbir işlem yapılmıyor ve bir alttaki komut ile programa devam ediliyor. Diyebiliriz ki LOOP komutu gizli olarak cx registerini kullanmaktadır.

inc dl : inc komutu karşısındaki registerin değerini bir artırır. Biliyorsunuz ki ekrana gönderilecek karakter dl içinde. Biz 255 döngü içinde 1 ila 255 arasındaki tüm ASCII karakterleri ekrana basmak istiyoruz. Bunun için toplama işlemi de yapabilirdik (add dl,1) fakat bu işlemin okunurluğu daha iyidir.

int 21 : Ekrana karakter yazma fonksiyonu çağırılıyor.

loop 105 : CX sıfıra eşit değil ise 105 adresine zıpla.

int 20 : Programdan çık.

Aşağıda g (go) komutu ile çıktı alınıyor. Tabii buradaki karakter seti dos altındaki; windows altındaki ile uyuşmayabilir. Daha sonra ise ascii.com şeklinde kaydediliyor.

 

Aşağıda bazı assembly komutları ve açıklamaları bulunuyor:

MOV : Değer aktarmak için kullanılır.  mov ah,2 mov ax,FFFF mov bx,cx mov dl,bh mov [200],ah mov dl,[382] gibi. (Köşeli parantez 'adresindeki' anlamındadır. Yani ah içeriğini 200 adresine transfer et gibi.)
XCHG : İki registerin içeriklerini karşılıklı olarak değiştirmelerini sağlar. xchg ah,bh xchg ax,dx gibi.
ADD : İki değerin toplanması amacıyla kullanılır. Sonuç değer soldaki registere kaydedilir. add ax,bx add ah,AF add dl,1 add al,ah gibi.
SUB : Çıkarma işlemi yapar. Sonuç ilk registere kaydedilir. sub ah,1 sub ax,bx sub cx,10 gibi.
MUL : Çarpma işlemi yapar. Tabii ki tüm işlemlerde olduğu gibi değerler ve sonuçlar hexadecimaldir. 3 farklı durumda incelenebilir.

8bit*8bit yani byte*byte : Çarpılacak değerlerden biri al ye kaydedilir mov al,5F gibi. Daha sonra çarpma işlemi yapılır, mul bl gibi. Sonuç ise ax registerindedir. (ax=al*bl işlemi)
16bit*16bit yani word*word : Çarpılacak değerlerden biri ax ye kaydedilir mov ax,05FF gibi. Daha sonra çarpma işlemi yapılır, mul bx gibi. Sonuç ise dx,ax ikilisine aktarılmıştır. Yani üst 16bitlik kısım dx de, alt 16bitlik kısım ise ax dedir. (dx,ax=ax*bx)
32bit*32bit yani dword*dword Çarpılacak değerlerden biri eax ye kaydedilir mov eax,A055FF10 gibi. Daha sonra çarpma işlemi yapılır, mul ebx gibi. Sonuç ise edx,eax ikilisine aktarılmıştır. Yani üst 32bitlik kısım edx de, alt 32bitlik kısım ise eax dedir. (edx,eax=eax,ebx)
 

DIV : Bölme işlemi için kullanılır. Ayrıntılarına girmiyorum.
INC : Register içeriğini bir artırır. inc ah gibi
DEC : Register içeriğini bir azaltır. dec cx gibi.
NEG : Sayının ikili tamamlayıcısını bulur. Örneğin al de 11110000 binary değeri bulunuyor ise neg al işleminden sonra bu değer 00001111 olur.
PUSH : Stack a yani stack segmente değer göndermek için kullanılır. push ax gibi.
POP : Stack dan değer çekmek için kullanılır. pop cx gibi.
 

Windows Altında Win32Assembly Programlama

Artık masm ile nasıl program yazılır bir gözatalım. Aşağıdaki küçük bir windows uygulaması yer almakta:

.386
.model flat, stdcall
option casemap :none
; case sensitive

include
masm32includewindows.inc
include
masm32includeuser32.inc
include
masm32includekernel32.inc

includelib
masm32libuser32.lib
includelib
masm32libkernel32.lib


.data
start

baslik db "İlk Assembly Programımız",0
yazi db "Merhaba Dünya",0

.code

 

start:

push MB_OK
push offset baslik
push offset yazi
push NULL
call MessageBox

push NULL
call ExitProcess

end

 

 

; --------------- Data section

.data

baslik db "İlk Assembly Programımız",0
yazi db "Merhaba Dünya",0

; --------------- Code section

.code

; --------------- Program startup
start:

invoke MessageBox,NULL,offset yazi,offset baslik, MB_OK
invoke ExitProcess,0

end start

 

Yukarıdaki ilk örnek qeditor.exe ile derlenebilecek haldedir. Biz eğer notepad kullanmış olsaydık bu şekilde kodlayacak idik. Ben derledim ve boyutu 2.5kbyte olan bir exe dosyası oluştu. Yukarıdaki hem soldaki hemde sağdaki assembly kodu aynı işi yapıyor. Yaptığı iş baslik ismi ile belirtilen başlığa sahip, yazi ismi ile yine data segmentte belirtilen yazıya sahip bir mesaj kutusu gösteriyor. OK'e tıklandıktan sonra program sonlanıyor. İsterseniz hemen açıklamaya başlayayım. İlk üç satıra uzun süre hiç dokunmayacaksınız, burayı geçiyorum. Include satırları ile içinde windows api fonksiyonlarının ve bazı sabit değerlerin isimleri bulunan inc uzantılı dosyaları tanımlıyoruz. Includelib fonksiyonu ile kullandığımız api fonksiyonlarını içeren kütüphaneleri tanımlıyoruz. Windows.inc dosyasında NULL ve MB_OK tanımlamalarının matematiksel eşitlikleri bulunuyor. Birçok özel durumun hangi sayılar ile belirtildiğini ezberlemektense bunların karşılıklarını bilmek daha kolaydır, hemde programın okunurluğunu artırır. .data ile data segment tanımlanıyor ve bu data segment içinde baslik ve yazi adreslerinde ilgili string ifadeler yer alıyor. Dikkat ederseniz stringlerin bittiğini belirtmek için ayrıca 0h degeri ekleniyor. Daha sonra .code ifadesi ile cede segment tanımlanıyor. start: ve end start ifadeleri arasına kodlarımızı yazıyoruz. Dört adet push komutu alt alta yazılmış. PUSH komutu ile stack segmente bazı değerler gönderiyoruz. Bunlar mesaj kutusu ile ilgili özellikler. İlk özellik mesaj kutusunda sadece OK tuşu bulunacağı anlamında. İkinci özellik olan mesajın başlık bölümünde yer alacak yazının adresini stacka yolluyoruz. (adresi anlamına gelen offset terimi kullanılmıştır) Üçüncü yollanan özellik mesaj olarak gösterilecek string ifadenin adresi. Son yollanan ifade NULL yani sıfırdır, buraya bir değer girmemize şu anda gerek yok. Gerekli parametreler stacka yollandıktan sonra fonksiyonumuzu çağırıyoruz. CALL komutu bir fonksiyonun yada bir altprogramın çağırılmasında kullanılır. Çağırılan yerdeki işler bittikten sonra programımız bir sonraki ifade ile devam edecektir. Fonksiyonumuz MessageBox apisidir . Bu api user32.inc içinde tanımlanmış ve ilgi kutuphane user32.lib eklenmiştir. Bu apiyi kullandığımızı belirtmek için yukarıdaki ifadeleri include ile tanımlamaktayız. Daha sonraki bölümde programdan çıkış kodları bulunuyor. Öncelikle çıkış kodu stacka yollanır. Normal sonlanmalarda bu NULL yani sıfırdır. Daha sonra kernel32.inc ve kernel32.lib ile tanımladığımız fonksiyonlardan ExitProcess çağırılıyor.

İşletim sistemimizde user32.dll içinde MesageBox apisi, kernel32.dll içinde ise ExitProcess apisi bulunmaktadır. Dos altında yaptığımız interrup çağrıları gibi windows altında api fonksiyonları bulunur. Bunlar disk erişiminden, ekrana resim çizdirmeye, buttonlar ve kutucuklar hazırlamaya kadar binlerce fonksiyon içeririler. Tabii sadece bu iki dll dosyasıyla sınırlı değil apiler. Ayrıca yukarıdaki komutları yazarken dikkatli olmanız gerekiyor. Derleyicimiz büyük küçük harf ayrımı yapabiliyor. Özellikle api fonksiyonların isimlerini aynen yazın.

Soldaki kod ise işlev olarak aynıdır. Farkı ise MAsmEd altında yukarıdaki gibi yazılması yeterlidir. Include tanımlamalarını programda sol taraftaki ağaç şeklindeki kısımdan ekleyerek yapıyoruz.  Data segmentin içeriği aynı. Code segmentde ise küçük bir değişiklik var. Alt alta yazdiğimiz fonksiyon çağırma işinde call yerine invoke ile tek satırda tüm parametreleri yazdık. Böylece daha kolay okunur oldu. Bunu qeditor.exe içinde de yapabilirdik, yinede eski şekilde kullanılan örnekler daha çok olduğu için ikisini de göstermek istedim. Yoksa yazım farkından başka ikiside aynen ilk örnekteki gibi çalışmaktadır. Birde editörümüz programımız için ikon ve ek bazı versiyon bilgileri eklemektedir. Bu nedenle editör içinden derlenen aynı program 5.5kbyte  boyuta sahip olacaktır.

 

 
 
  Bugün 186 ziyaretçi (205 klik) buradaydı  
 
Bu web sitesi ücretsiz olarak Bedava-Sitem.com ile oluşturulmuştur. Siz de kendi web sitenizi kurmak ister misiniz?
Ücretsiz kaydol