Bildiğiniz gibi bilgisayarlar, işlemcilerden oluşur. İşlemcilerin ise, kendilerine has dilleri vardır. Bu dile, kısaca 0 ve 1’lerden oluşan, işlemcinin Bitler vasıtasıyla iletişim kurduğu dil diyebiliriz. Makine dilinin nasıl kullanılacağı işlemci türüne, mimarisine göre değişir.
İşlemciyle ancak makine dilini kullanarak anlaşabilirsiniz. Bu dil, alıştığımız programlama dillerine göre oldukça farklıdır ve öğrenmesi zordur. Zaten temelinde bu zorluk ve bir noktada işin çok uzaması sebebiyle, günüzdeki programla dilleri var. (C, Java, Python gibi). Bu dilleri kullanırken compiler (derleyici) diye bir terim duymuşsunuzdur. İşte compiler, sizin Python ya da C gibi programlama dilinde yazdığınız kodu, işlemci mimarinize uygun makine diline çeviirir. Bu sayede işlemciniz, yazdığınız kodu anlar ve çalıştırır.
Assembly, makine diline oldukça yakın bir dildir. Neredeyse makine diline karşılık programlar yazabilirsiniz. Hatta breadboarda yapılmış 8 Bit bilgisayarlar bile uygun Assembly ile programlanabilir. Bu yakınlık sebebiyle de “Low level” dil olarak geçer. Low ve High level terimleri karşınıza çokça çıkmıştır. O nedenle şu diagramı bırakmak istiyorum:
Assembly dilinde yazılmış programlar bir Assembler tarafından derlenir. Her Assembler’ın belirli bir bilgisayar mimarisi için tasarlanmış kendi Assembly dili vardır. Bu nedenle X86 mimaride kullandığınız Assembly kodu ile ARM (RISC) mimaride işlem yapamazsınız. Bu nedenle de aynı makine dili gibi, taşınabilir değildir.
RISC ve X86 arasında temel farklar var. Zaten bu farklar, RISC’i X86’ya göre daha sade yapıyor. Ben de hem RISC hem de yeni trendin ARM olması sebebiyle, örneklerimi ARM diliyle vermeye karar verdim.
X86 ve ARM arasındaki farklar için Technopat.net’te kaleme aldığım yazıya bakabilirsiniz:
VisUAL2
Bahsettiğim gibi, Assembly diline derlemek için Assembler’e ihtiyacımız var. ARM ile çalışacağımızdan, bu işi X86 makinelerimizde yapmak için, Assembler’e ek bir emülatöre de ihtiyacımız var.
VisUAL2, hem Assembler hem de emülatör görevini tek başına yerine getiren, beğendiğim güzel bir proje. GİtHub reposuna buradan ulaşabilirsiniz. Yine buradan indirebilirsiniz:
Kurulumu yapıp çalıştırdıktan sonra, karşınıza şöyle bir ekran çıkacak:
Üst menüden başlayalım:
Sağ menüden devam edelim:
Basit işlemler – MOV ve ADD
Standart bir kod ile başlayalım:
MOV R1, #0x11
MOV R2, #0x5
MOV R3, #0x6
ADD R0, R1, #0x8
Şimdi, kodu çalıştıralım ve sonuca bakalım:
Gördüğünüz gibi sağdaki register değerlerimiz değişmiş oldu.
Peki burada ne yaptık?
Yani aslında yaptığımız şey, alışık olduğmuz dilde şu şekilde ifade ediliyor:
a = 17
b = 5
c = 6
d = a + 8 (=25)
Bu örneğin dışında, SUB
komutunu da ADD
gibi kullanabilirsiniz:
Memory’e yazma ve Memory’den veri alma
İlk örneğimizde basit bir şekilde registerlara veri yazdık ve bunları yönettik. Bu örneğimizde belleğe nasıl değer yazacağımızı ve yazılı değeri nasıl okuyacağımızı göreceğiz.
Pointers
Belleği, apartmanınızın posta kutuları gibi düşünebilirsiniz. Nasıl her kutunun bir numarası varsa, bellekteki her alanın da bir adresi var. Bu adresler oldukça önemli zira adresi bilmeden veri yazamazsınız ya da var olan veriyi bulamazsınız. Adresleri de, diğer veriler gibi değişkenlerde tutuyoruz. Assembly’de, bellek adreslerini registerlara kaydederiz ve o register artık pointer haline gelir. (Çok benzer bir durum C’de de vardır. )
Pointer, bize belleğin adresini söyleyebileceği gibi, ilgili adresteki veriyi de söyleyebilir. Yani:
R1 registerında 0x20000040
adresinin kayıtı olduğunu düşünelim. R1
, bize adresi dönecekken (aynı değişkenin değeri dönmesi gibi); [R1]
komutu bize, registera kayıtlı adresteki değeri döner.
LDR ve STR
Belleğe veri yazmak ve bellekten veri almak için, MOV ve ADD komutları gibi komutlara ihtiyacımız var. Bu iş için özelleşmiş iki komut bulunmaktadır:
Örnek bir kod ile ne yaptığımıza bakalım:
mov R0, #0x0B
mov R1, R0
add R2, R3, #0x4
add R3, R1, #0x0A
LDR R4, =0x20000040
LDR R5, =0x20000050
STR R2, [R4]
STR R3, [R5]
Ve, kodumuzu çalıştıralım. Bu sefer, Register bölümüne ek olarak, Memory bölümü de değişmiş olacak:
Kodun ilk 4 satırında, ilk örnekte de gördüğümüz MOV ve ADD ile basit işlemleri yaptık. 6. ve 7. satırda ise LDR
komutuyla Bellek adreslerimizi ilgili registerlara atadık.
9. ve 10. satırdaki STR
kodlarıyla da, R2
ve R3
registerında kayıtlı değerleri, [R4]
ve [R5]
pointerları ile ilgili bellek adreslerine yazdık. Gördüğünüz gibi, STR komutunu kullanırken, Belleğe yazmak için registerı değil pointerı kullandık.
Symbols
Hatırlarsanız, programın sağ tarafında üçüncü bir bölüm daha vardı: Symbols
.
Symbolse örnek vermek için, az önce belleğe veri yazmak için kullandığımız kodu değiştirebiliriz:
Mem_Addr_1 equ 0x20000040
Mem_Addr_2 equ 0x20000050
mov R0, #0x0B
mov R1, R0
add R2, R3, #0x4
add R3, R1, #0x0A
LDR R4, =Mem_Addr_1
LDR R5, =Mem_Addr_2
STR R2, [R4]
STR R3, [R5]
Aslında çok anlaşılır bir olay. Bellek adresini, bir sembole atadık.
Branch and Control
Assembly dilinde if
, while
, for
gibi operatörler yoktur. Bunların yerine conditional branching denen yöntem uygulanır, ancak bu başka bir yazının konusu olsun.
Çok derinlere dalmadan, meraklısı için keyifle okuyup, deneyebileceği, rehber/makale karışık bir yazı hazırlamak istedim. Ve bunun sonucunda bu yazı ortaya çıktı. Burada bazı noktaları sadeleştirerek anlatmaya çalıştım, bu nedenle içi boş kalmış olabilir ya da bu konuda bilgili biriyseniz, size tam da doğru gelmeyebilir. Bunu yorum olarak da belirtebilirsiniz. Yine bazı noktaları anlamak için programlamayla az çok haşır neşir olmuş olmak gerekebilir.
Assembly, özellikle işlemcilerle/mikroişlemcilerle çalışıyorsanız, C/C++ ile iç içeyseniz, insana farklı bir bakış açısı kazandırıyor. Ben tercihimi RISC’den dolayı ARM Assembly’den kullanmıştım ve onu öğrenmeye çalıştım.
Bu noktada, ARM Assembly için ileri çalışmalarda işinize yarayacak bir kaynak bırakmak istiyorum:
Umarım yazı hoşunuza gitmiştir. Okuduğunuz için teşekkürler.
Fikir ve yorumlarınızı belirtirseniz çok mutlu olurum.