Rehber ARM Assembly - VisUAL2 ile basit ARM Assembly örnekleri

Merhaba,

Assembly ile alakalı birkaç örnek koddan oluşan bir konunun ilgi çekeceğini düşündüm. VisUAL''den dolayı da ARM Assembly ile yapmak istedim. :) Öncesinden belirtmeliyim ki bu bir örnek kod konusu, rehber değil. Assembly hakkında kısaca bilgi vereceğim ama "Assembly nedir, ne yapar, ne işe yarar" gibi sorulara cevap vermeyecektir. Zira bu, uzun bir rehber gerektiriyor. Sadece birkaç örnek kod paylaşacağım.

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 yazımıza 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. Size nasıl geldiğini yorumlarda belitrebilirsiniz. :)

Ö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 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. :)
 
Son düzenleme:

Akil Gündoğan

Terapat
Katılım
2 Temmuz 2014
Mesajlar
9.924
Makaleler
26
Çözümler
191
Yer
Türkiye - İşler güçler...
Mekanın sahibi geri geldi :)

Mükemmel yazı hocam, elinize emeğinize sağlık.
 
KS
İbrahim Zdemir

İbrahim Zdemir

Terapat
Katılım
2 Haziran 2015
Mesajlar
11.648
Makaleler
96
Çözümler
107
Yer
İstanbul
Mekanın sahibi geri geldi :)

Mükemmel yazı hocam, elinize emeğinize sağlık.

Estağfirullah. :)

Bu yazı, başlamak isteyenler için bir başlangıç niteliğinde. Türkçe kaynaklar, altın değerinde. Bu nedenle ne katarsak, kârdır.

@bitwise görüşlerinizi almak isterim doğrusu. :)
 

YAHYA NUROĞLU

Centipat
Katılım
22 Nisan 2020
Mesajlar
3.094
Makaleler
1
Çözümler
6
Yer
IDE HDD'lerin içi.
Üşenmeden yazmışsınız hocam. Elinize ve klavyenize sağlık.🙂
 
KS
İbrahim Zdemir

İbrahim Zdemir

Terapat
Katılım
2 Haziran 2015
Mesajlar
11.648
Makaleler
96
Çözümler
107
Yer
İstanbul
KS
İbrahim Zdemir

İbrahim Zdemir

Terapat
Katılım
2 Haziran 2015
Mesajlar
11.648
Makaleler
96
Çözümler
107
Yer
İstanbul

bitwise

Hectopat
Katılım
22 Mart 2018
Mesajlar
3.877
Çözümler
35
Merhaba,

Assembly ile alakalı birkaç örnek koddan oluşan bir konunun ilgi çekeceğini düşündüm. VisUAL''den dolayı da ARM Assembly ile yapmak istedim. :) Öncesinden belirtmeliyim ki bu bir örnek kod konusu, rehber değil. Assembly hakkında kısaca bilgi vereceğim ama "Assembly nedir, ne yapar, ne işe yarar" gibi sorulara cevap vermeyecektir. Zira bu, uzun bir rehber gerektiriyor. Sadece birkaç örnek kod paylaşacağım.

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.

Eki Görüntüle 793322

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

Eki Görüntüle 792775

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 yazımıza 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:

Eki Görüntüle 792837

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

Eki Görüntüle 792880

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. Size nasıl geldiğini yorumlarda belitrebilirsiniz. :)

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

Eki Görüntüle 792976

Eki Görüntüle 792975

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]

Eki Görüntüle 793002

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. :)
Cok guzel olmus zevkle okudum : )

Ben de bildigim(i dusundugum) bazi seyleri ekleyeyim, konuya katkim olsun kuru kuru likelamak disinda.

ARM'ye neden ihtiyacimiz var? Cunku ARM x86 mimarisinde yapilabilen islemlerin sadece bir bolumunu yapan ancak bunu enerji efektif halledebilen bir mimari. Enerjiden kastim direkt elektrik enerjisi, zira ARM mimarisinde her operasyon tek bir clock cycle icerisinde yapilabilir. Nedir clock cycle? Islemcinin GHz degeri ile kastettigimiz sey, 1 GHz frekansta calisan ARM islemcisi, bu ornekte verilen "mov" ya da "add" operasyonlarindan saniyede 1 milyar defa yapabilir. x86 mimarisinde farkli operasyonlar farkli pipeline akisindan gectikleri icin 1'den fazla cycle a ihtiyac duyulabilir. Her operasyonun tek cycle'da olmasi "scalar" ozelliktir. ARM icin yazilan bir kod blogunun tam olarak kac cycle da calisacagini kesin bilirsiniz.

x86 mimarisinde ise "superscalarity" denilen bir mesele vardir, islemler sirasina gore paralelize edilirler. 1. satirda yazilan sey, 2. satirda yazilandan sonra execute edilebilir. Neden? Cunku her islem 1 cycle da olmadigindan, 2. satirda 1 cycle da halledilebilecek kisa bir is varsa ve bagimsizsa oncelikle islemci pipeline'a onu alir, zira 1. satirdaki islem belki 3. cycle isteyecektir.
Bu ozellik dogru kullanilirsa, yani assembly kodlari dogru sira ile organize edilirse ciddi performans kazanci olur.
Kod:
z = cos(pi*0.78)
x = 1 + 2
y = x + 3
Ustteki ornekteki 1. satirdaki islem pahali bir islem , 2. ve 3. satirdaki islemler kolay fakat birbirine bagimli ( 3. islem icin 2. nin cozumunu bilmemiz gerekiyor). Fakat 1. islem bagimsiz, 2 ve 3 ile paralelize edilebilir. x86 mimarisinde yazdiginiz bu kodu akilli bir compiler dogru sira ile paralelize edilebilecek sekilde yazacaktir ve ARM esleneginden daha hizli calisacaktir. Bu ozelligin dezavantaji tabi neredeyse 10 katina varan guc tuketimi.

Yine x86 mimarisi direkt memory uzerinden hesaplama yapabilirken ARM memory'den register'a load etmenizi ister. Bu da memory deki 2 sayiyi toplamak icin 2 ekstra assembly komutu demektir ve 2 cycle a mal olur.

Yine de bildigim kadariyla konusuyorum, x86 uzerinde yazilmis her seyi ARM ye adapte edebiliriz. Hayatimizi kolaylastiran komut setleri olsa da ARM de de ayni sonucu elde etmenin yolu var.

Tekrar soyleyeyim, cok guzel bir yazi olmus. Yillardan sonra tekrar oturup assembly yazasimi getirdi : )
 
KS
İbrahim Zdemir

İbrahim Zdemir

Terapat
Katılım
2 Haziran 2015
Mesajlar
11.648
Makaleler
96
Çözümler
107
Yer
İstanbul
@bitwise çok teşekkürler eklemelerin için, yazı daha da değerlendi. :)

Clock cycle hakkında dediklerin çok doğru. X86 ve ARM arasında farklar var. Bunun dışında bir satır birden fazla cycle'a mal olabiliyor. VisUAL2'nin clock kısmında kaç instructionın kaç cyclea tekabül ettiği de belirtiliyor. Mesela son örnekteki kod için 8:15 yazılmış, yani 8 instruction için 15 cycle.

Yine X86'da memory üzerinde işlem yapılabilmesi, "superscalarity" gibi özellikler, RISC'i ve X86'yı ayıran bariz özelliklerden bazıları.

Bence mikroişlemcilerle, işlemcilerle ve sistemlerle uğraşan birileri, ucundan kıyısından platformu için uygun olan Assembly'i öğrenmeli. Gerçekten farklı bir bakış açısı kazandırıyor. Mesela normalde programlarda alıştığımız if, else, for, while gibi operatörleri Assembly'de farklı şekilde entegre etmek gerekiyor. Ben şaşırmıştım mesela ilk kez görünce.


Bize normalde Sistem derslerinde X86 üzerinden göstermişlerdi ancak oradan ilerleyememiştim pek. Bu yaz, ARM mikroişlemcili kartlarla çalışmaya başlayınca, işin Assembly tarafına da bakmaya başladım ve hoşuma da gitti. Hatta sonradan öğrendim ki Sistem derslerini yeni sınıflara ARM Assembly olarak vermeye başlamışlar.

ARM ve RISC yeni bir trend artık. Hatta RISC-V ile işler başka boyutlara kayıyor... :)
 

Assembly

Femtopat
Katılım
21 Aralık 2020
Mesajlar
70
Merhaba,

Assembly ile alakalı birkaç örnek koddan oluşan bir konunun ilgi çekeceğini düşündüm. Visual"den dolayı da ARM Assembly ile yapmak istedim. :) Öncesinden belirtmeliyim ki bu bir örnek kod konusu, rehber değil. Assembly hakkında kısaca bilgi vereceğim ama "Assembly nedir, ne yapar, ne işe yarar" gibi sorulara cevap vermeyecektir. Zira bu, uzun bir rehber gerektiriyor. Sadece birkaç örnek kod paylaşacağım.

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.

Eki Görüntüle 793322

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

Eki Görüntüle 792775

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 yazımıza 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. GitHub reposuna buradan ulaşabilirsiniz. Yine buradan indirebilirsiniz:


Kurulumu yapıp çalıştırdıktan sonra, karşınıza şöyle bir ekran çıkacak:

Eki Görüntüle 792837

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

Eki Görüntüle 792880

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. Size nasıl geldiğini yorumlarda belitrebilirsiniz. :)

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

Eki Görüntüle 792976

Eki Görüntüle 792975

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]

Eki Görüntüle 793002

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. :)

Güzel bir konu olmuş hocam. Meraklı arkadaşlara Assembly hakkında özgün Türkçe yazı bulmak çölde su bulmak gibi. Elinize sağlık.
 
Yukarı