Assembly Dili ile Örnek Uygulama Yazmak

Assembly dili konuşulurken çoğu zaman işin zor tarafı öne çıkarılıyor. Oysa küçük ve anlamlı bir örnek üzerinden ilerlediğimizde, assembly’nin ne yaptığını çok daha net görmeye başlıyoruz. Bu yazıda Flat Assembler kullanarak console olarak derlenen, kullanıcıdan iki sayı alan ve bu sayıları toplayıp sonucu veren küçük bir uygulama geliştiriyoruz. Buradaki hedef sadece çalışan bir program görmek değil; yazdığımız her bölümün ne anlama geldiğini de net biçimde kavramak.

Bu yazıda ne yapıyoruz?

  • Flat Assembler ile bir PE console programı yazıyoruz.
  • Kullanıcıdan iki tam sayı alıyoruz.
  • Bu iki sayıyı topluyoruz.
  • Sonucu ekrana yazdırıyoruz.
  • Kodun içindeki her bölümün görevini tek tek açıklıyoruz.

Neden Flat Assembler?

Flat Assembler, Windows üzerinde doğrudan çalıştırılabilir çıktı üretmeyi kolaylaştıran, sade sözdizimiyle öne çıkan bir assembler. Özellikle başlangıç seviyesinde, gereksiz araç karmaşasına girmeden tek dosyada ilerlemek büyük avantaj sağlıyor. Burada amacımız sadece “bir şey derlemek” değil; yazdığımız kodun hangi bölümünün veri, hangi bölümünün yürütülebilir kod, hangi bölümünün dış fonksiyon import’u olduğunu da net biçimde görmek.

Örnek uygulama ne yapacak?

Program akışımız çok basit:

  • Kullanıcıdan birinci sayıyı isteyeceğiz.
  • Kullanıcıdan ikinci sayıyı isteyeceğiz.
  • Bu iki sayıyı toplayacağız.
  • Sonucu ekrana yazdıracağız.
  • Sonuç görünsün diye bir tuş bekleyeceğiz.

Bu küçük örnek sayesinde assembly tarafında hem veri tanımlamayı, hem işlem yapmayı, hem de dış fonksiyon çağırmayı aynı uygulama içinde görüyoruz.

Kullanacağımız yaklaşım

Burada doğrudan ReadConsole / WriteConsole ile ilerlemek yerine, örneği daha okunur ve öğretici tutmak için C runtime içindeki printf, scanf ve _getch fonksiyonlarını kullanıyoruz. Böylece dikkatimiz Windows konsol API ayrıntılarında dağılmadan, assembly’nin temel mantığına odaklanıyor.

Burada önemli ayrım

C runtime fonksiyonlarını çağırırken cinvoke, WinAPI fonksiyonunu çağırırken invoke kullanıyoruz. Bunun sebebi çağrı kuralı farkıdır. Yani sadece sözdizimi değil, çağrının nasıl yapıldığı da değişir.

Kodun tamamı

Aşağıdaki örnek, Win32 / x86 console uygulaması olarak derlenir.

format PE console 4.0
entry start

include 'win32ax.inc'

section '.data' data readable writeable
    msg1        db 'Birinci sayiyi girin: ',0
    msg2        db 'Ikinci sayiyi girin: ',0
    fmt_in      db '%d',0
    fmt_out     db 'Toplam = %d',13,10,0
    msg_exit    db 13,10,'Cikmak icin bir tusa basin...',0

    number1     dd ?
    number2     dd ?

section '.code' code readable executable
start:
    ; 1) Ilk sayiyi iste
    cinvoke printf, msg1
    cinvoke scanf, fmt_in, number1

    ; 2) Ikinci sayiyi iste
    cinvoke printf, msg2
    cinvoke scanf, fmt_in, number2

    ; 3) Toplama islemi
    mov eax, [number1]
    add eax, [number2]

    ; 4) Sonucu yazdir
    cinvoke printf, fmt_out, eax

    ; 5) Pencere hemen kapanmasin
    cinvoke printf, msg_exit
    cinvoke getch

    ; 6) Programdan cik
    invoke ExitProcess, 0

section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL', \
            msvcrt,   'MSVCRT.DLL'

    import kernel32, \
           ExitProcess, 'ExitProcess'

    import msvcrt, \
           printf, 'printf', \
           scanf,  'scanf', \
           getch,  '_getch'

Kod nasıl derlenir?

Dosyayı örneğin toplama.asm adıyla kaydediyoruz. Ardından komut satırında FASM ile aşağıdaki komutu çalıştırıyoruz:


fasm toplama.asm toplama.exe

Derleme başarılı olursa elimizde toplama.exe oluşur. Program çalıştığında kullanıcıdan iki sayı ister ve toplamı ekrana yazar.

Kodun bölümleri ne anlama geliyor?

format PE console 4.0

Bu satır, çıkacak dosyanın Windows üzerinde çalışan bir console uygulaması olacağını söyler. Yani burada programın hedef formatını tanımlıyoruz.

entry start

Program çalıştığında ilk olarak hangi etiketten başlayacağını belirtir. Burada başlangıç noktası start etiketidir.

include 'win32ax.inc'

Bu satır, FASM’ın Windows için sunduğu makroları içeri alır. Böylece invoke, cinvoke, library ve import gibi kullanışlı yapıları rahatça kullanabiliyoruz.

section '.data' bölümü

Bu bölümde verilerimizi tanımlıyoruz:

  • Kullanıcıya gösterilecek metinler
  • Giriş ve çıkış format dizeleri
  • İki sayı için ayrılan bellek alanı

number1     dd ?
number2     dd ?

Buradaki dd, 32 bitlik alan ayırır. ? ise “burada yer ayır ama başlangıç değeri verme” anlamına gelir.

section '.code' bölümü

Programın gerçekten çalışan kısmı burasıdır. Yani işlem yapan komutlar bu bölümde bulunur.

mov eax, [number1]
add eax, [number2]

Bu iki satır, birinci sayıyı EAX register’ına alır ve ikinci sayıyı bunun üzerine ekler. Sonuç artık EAX içindedir.

section '.idata' bölümü

Bu bölümde programın dışarıdan kullandığı fonksiyonların import bilgisi yer alır. Yani printf, scanf, _getch ve ExitProcess gibi fonksiyonların hangi DLL’den çağrılacağını burada tanımlarız.

Kodun akışı adım adım

1) İlk sayıyı istemek


cinvoke printf, msg1
cinvoke scanf, fmt_in, number1

Önce ekrana “Birinci sayiyi girin:” mesajını yazdırıyoruz. Ardından scanf ile kullanıcının girdiği sayıyı number1 alanına yazıyoruz.

2) İkinci sayıyı istemek

cinvoke printf, msg2
cinvoke scanf, fmt_in, number2

Aynı mantıkla ikinci sayı da alınır ve number2 alanına yazılır.

3) Toplama işlemi

mov eax, [number1]
add eax, [number2]

Burada assembly’nin en temel mantığını görüyoruz: veriyi al, register’a taşı, işlem yap. Sonuç doğrudan EAX içinde oluşuyor.

4) Sonucu yazdırmak


cinvoke printf, fmt_out, eax

fmt_out içinde bulunan %d yer tutucusuna bu kez EAX içindeki toplam değer yazdırılır.

5) Hemen kapanmaması

cinvoke printf, msg_exit
cinvoke getch

Programın çift tıklamayla açıldığında anında kapanmaması için kullanıcıdan bir tuş bekliyoruz.

6) Programdan çıkış

invoke ExitProcess, 0

Burada WinAPI tarafındaki ExitProcess çağrılır ve program 0 çıkış koduyla sonlandırılır.

Neden cinvoke ve invoke ayrı?

Çünkü çağrı kuralları farklıdır. C runtime fonksiyonları için cinvoke, WinAPI fonksiyonları için ise invoke kullanırız. Bu ayrım küçük gibi görünür ama assembly tarafında son derece önemlidir.

En sık yapılan hatalar

scanf için yanlış parametre vermek

Yeni başlayanların sık yaptığı hata, değeri değil adresi vermesi gereken yerde karışıklık yaşamaktır. Burada number1 ve number2 birer bellek alanıdır; scanf de yazacağı yeri ister.

invoke ile cinvoke karıştırmak

C fonksiyonları ile WinAPI fonksiyonları aynı çağrı kuralını kullanmadığı için burada dikkatli olmak gerekir.

Programın hemen kapanması

Özellikle console uygulamaları çift tıklanarak açıldığında sonuç görünmeden kapanabilir. Bu yüzden örneğe _getch eklemek faydalıdır.

Sonuç

Assembly dili ilk bakışta sert görünse de, küçük ve anlamlı örneklerle ilerlediğimizde her şey daha netleşiyor. Bu yazıda Flat Assembler ile console olarak derlenen basit bir uygulama geliştirip kullanıcıdan iki sayı aldık, bunları topladık ve sonucu ekrana yazdırdık. Daha önemlisi, bunu yaparken:

  • format PE console
  • entry
  • .data, .code, .idata
  • library, import
  • invoke, cinvoke
  • EAX ile toplama mantığı

gibi temel taşların ne işe yaradığını da görmüş olduk. Benim için assembly öğrenmenin doğru yolu tam olarak bu: önce çalışan küçük bir örnek kurmak, sonra her satırın ne yaptığını anlamak. Bu yaklaşım oturduğunda daha karmaşık örnekler de göz korkutmamaya başlıyor.

Yorum Bırakın

E-posta adresiniz yayınlanmayacaktır. Zorunlu alanlar * ile işaretlenmiştir