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.
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.
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 consoleentry.data,.code,.idatalibrary,importinvoke,cinvokeEAXile 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.