ARM Assembly - VisUAL2 ile basit ARM Assembly örnekleri

visUAL üçüncü kod.PNG

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.

gif-assembly-to-machine-code.gif

İş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:

1_-OVqIc67l_mzm-M6raZeCA.png



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:

visUAL ana ekran.PNG



Üst menüden başlayalım:

  • Run Assembly kodunu çalıştırır
  • Reset programı resetler
  • Step tuşları adımlar arasında gitmenizi sağlar, bu sayede Clock cycle başına hangi satırın çalıştığını görebilirsiniz. Aynı zamanda program adım adım çalışır.
  • Clock programı çalıştırmak için ne kadar clock cycle gerektiğini bize gösterir. Clock cycle, aslında işlemcinin saat hızı ile bağlantılıdır ve size, programın ne kadar sürede çalışacağı ile ilgili bilgi verir

Sağ menüden devam edelim:

  • Registers Registerler işlemci çalışması sırasında farklı amaçlar için kullanılan değişkenlerdir. Bellekteki verilere ulaşmak belirli bir zaman gerektirir, fakat registerler işlemci çekirdeğindedir ve fazladan zaman harcanmadan istenen işleme göre içerikleri kullanılabilmektedir. Ancak sınırlı sayıda bulunurlar. Registerler genel amaçlı kullanılabilecekleri gibi bazıları sadece özel görevleri üstlenmektedir. (Mesela burada, R13'ün özel bir görevi vardır, gördüğünüz gibi önceden tanımlanmıştır.)
  • Memory belleğimiz
  • Symbols insanların anlayabileceği değişkenler

Basit işlemler - MOV ve ADD

Standart bir kod ile başlayalım:

Kod:
MOV     R1, #0x11
       MOV     R2, #0x5
       MOV     R3, #0x6
       ADD     R0, R1, #0x8


Şimdi, kodu çalıştıralım ve sonuca bakalım:

visUAL ilk kod.PNG



Gördüğünüz gibi sağdaki register değerlerimiz değişmiş oldu.

Peki burada ne yaptık?

  • MOV R1, #0x11 MOV komutu, boş bir registera değer yazmamıza ya da bir registerdeki değeri, eşit kapasitedeki başka bir registera kopyalamamıza olanak tanır. Burada, R1 registerına, 0X11 Hex değerini giriyoruz. 2. ve 3. satırlarda da farklı registerlara farklı değerler giriyoruz.
    • Syntax: MOV <yazılacak register>, <yazılacak değer ya da kopyalanacak register>
  • ADD R0, R1, #0x8 ADD komutu bir registera, başka iki registardaki değeri toplayarak yazmamıza, ya da bir registardaki değerle başka bir değeri toplayıp yazmamıza olanak tanır. Burada, R1 registarındaki değeri, 0x8 Hex değeriyle toplayıp, R0 registerına giriyoruz.
    • Syntak ADD <yazılacak register>, <toplanacak register>, <toplanacak deger ya da başka bir register>


Yani aslında yaptığımız şey, alışık olduğmuz dilde şu şekilde ifade ediliyor:

Kod:
a = 17
b = 5
c = 6

d = a + 8 (=25)


Bu örneğin dışında, SUB komutunu da ADD gibi kullanabilirsiniz:

  • SUB R5, R3, #0x06 gibi.


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:

  • LDR bellek adresini registera kaydetmemize olanak tanır.
    • LDR R4, =0x20000040
  • LDR, bellekteki değeri registera kaydetmemize olanak tanır.
    • LDR R4, [R0] --> ([RO] pointerdır)
  • STR registerdaki içeriği, belleğe kaydetmemize olanak tanır.
    • STR R5, [R1] --> ([R1] pointerdır)
Bana C'ye göre daha anlaşılır geldi. :)

Örnek bir kod ile ne yaptığımıza bakalım:

Kod:
       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:

visUAL ikinci kod.PNG



ikinci kod mem.PNG



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:

Kod:
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]


visUAL üçüncü kod.PNG



Aslında çok anlaşılır bir olay. Bellek adresini, bir sembole atadık.

Branch and Control

Assembly dilinde ifwhilefor 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. :)

Comments