Birkaç gün önce ilk blog gönderisini yazdım - x64 assembly giriş - x64 Assembly’e merhaba deyin [bölüm 1] büyük ilgi çekmesi beni çok şaşırttı:
Öğrenme yolumu açıklamak için beni daha da motive ediyor. Bu günlerde farklı insanlardan birçok geri bildirim aldım.Çok hoş sözler vardı, ama benim için daha önemli olan birçok tavsiye ve yeterli eleştirmenin var olmasıydı.Özellikle harika geri bildirimleri için teşekür etmek istediğim:
Fiennes
Grienders
nkurz
Ve Reddit ve Hacker News'de tartışmaya katılan herkes. Pek çok görüş vardı, ilk kısım yeni başlayanlar için çok net olmamasıydı, bu yüzden daha bilgilendirici yazılar yazmaya karar verdim.Hadi, x86_64 assembly’e merhaba deyin ikinci bölüm ile başlayalım.
Yukarıda yazdığım gibi farklı insanlardan ilk yazının bazı bölümlerinin net olmadığına dair pek çok geri bildirim aldım.Bu yüzden, bu ve sonraki bölümlerde göreceğimiz bazı terminolojik açıklamalardan başlayalım.
Register(Yazmaç) - işlemcinin içinde küçük bir miktar depolama alanıdır.İşlemcinin ana noktası veri işlemedir. İşlemci hafızadan veri alabilir ancak yavaş bir işlemdir. İşte bu yüzden işlemci register denilen kendi dahili sınırlarında olan veri depolama setine sahiptir.
Little endian- hafızayı büyük bir dizi olarak hayal edebiliriz. Bayt içerir. Her adres bellek “dizisi”nin bir elemanını saklar. Her eleman bir bayttır. Örneğin 4 baytımız var: AA 56 AB FF. Küçük endianda en az anlamlı byte en küçük adrese sahiptir:
0 FF
1 AB
2 56
3 AA
0, 1, 2 ve 3 hafıza adresleridir.
Big-endian - Big endian, little endian'a göre baytları ters sırada saklar. Yani, AA 56 AB FF bayt dizisine sahip olursak:
0 AA
1 56
2 AB
3 FF
Syscall(Sistem Çağrısı) - Bir kullanıcı seviyesi programının, işletim sisteminden bir şey yapmasını istediği yoldur. Burada syscall tablosunu bulabilirsiniz.
Stack(Yığın) - işlemcinin çok kısıtlı bir register sayısı vardır.Bu yüzden, stack sürekli adreslenebilir RSP, SS, RIP ve bebzeri olan özel register'lara sahiptir.Sonraki bölümlerde stack'e daha yakından bakacağız.
Section(bölüm) - her assembly programı bölümlerden oluşur.Aşağıdaki bölümler vardır:
- data section - ilk değer atanmış verilerinin veya sabitlerin bildirilmesi için kullanılır.
- bss section - ilk değer atanmamış değişkenleri bildirmek için kullanılır.
- text section - kod için kullanılır.
General-purpose registers - 16 genel amaçlı register vardır - rax, rbx, rcx, rdx, rbp, rsp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15. Elbette, assembly programlaması ile ilgili terimlerin ve kavramların tam listesi değildir.Bir sonraki blog yazılarında başka garip ve yabancı bir kelime ile karşılaşırsak bu kelimelerin açıklaması olacaktır.
Temel veri türleri bayt, word, doublewords, quadwords ve double quadwords'tir. Bir bayt sekiz bittir, bir word 2 bayttır, bir doubleword 4 bayttır, bir quadword 8 bayttır ve bir double quadword 16 bayttır (128 bit).
Şimdi yalnızca tam sayılarla çalışacağız, o zaman görelim. İki tür tamsayı var: işaretli(signed) ve işaretsiz(unsigned).İşaretli tamsayılar ve işaretsiz ikili(binary) sayılar byte, word, doubleword ve quadword içeririr.Değerleri, bir işaretsiz bayt tamsayı için 0 ile 255 arasında değişir,işaretsiz bir word tamsayısı için 0'dan 65,535'e, işaretsiz bir doubleword tamsayısı için 0'dan 2 ^ 32 - 1'e ve işaretsiz bir quadword tamsayısı için 0 ile 2 ^ 64 - 1 arasında değişir.İşaretli tamsayılar ve işaretli ikili sayılar işaretsiz byte,word ve benzerlerinde tutulur.İşaret biti negatif tamsayılar için atanır, pozitif tamsayılar ve sıfır için temizlenir.Tamsayı değerleri bayt tamsayısı için –128 ila +127 arasında, bir word tamsayı için –32,768'den +32,767'ye, bir doubleword tamsayısı için –2 ^ 31 ila + 2 ^ 31 - 1 arası ve bir quadword tamsayısı için –2 ^ 63 ila + 2 ^ 63 - 1 arasında değişir.
Yukarıda yazdığım gibi, her assembly programı bölümlerden oluşur, data section, text section ve bss section olabilir.Data section'a bakalım. Ana nokta başlangıç sabitlerini bildirmek. Örneğin:
section .data
num1: equ 100
num2: equ 50
msg: db "Toplam doğru", 10
Tamam, neredeyse her şey burada açık. num1, num2, msg ve değerleri olan 3 sabit 100, 50 ve "Toplam doğru",10. Ama db, equ nedir? Aslında, NASM bir dizi sözde komutu desteklemektedir:
- DB, DW, DD, DQ, DT, DO, DY ve DZ - ilk değer atanmış verilerinin bildirilmesi için kullanılır. Örneğin:
; 4 byte tanımla 1h, 2h, 3h, 4h
db 0x01,0x02,0x03,0x04
;; word'u 0x12 0x34 olarak tanımla
dw 0x1234
- RESB, RESW, RESD, RESQ, REST, RESO, RESY ve RESZ – ilk değer atanmayan değişkenleri bildirmek için kullanılır.
- NCBIN - harici ikili dosyaları içerir.
- EQU - sabitleri tanımlar. Örneğin:
;; şimdi, bir 1' e eşittir.
bir equ 1
TIMES - Yinelenen komutlar veya veriler.(açıklama sonraki gönderilerde olacak.)
Aritmetik talimatların kısa bir listesi:
- ADD - tamsayı ekle
- SUB - çıkarma
- MUL - işaretli çarpma
- IMUL - işaretsiz çarpma
- DIV - işaretsiz bölme
- IDIV - işaretli bölme
- INC - artma
- DEC - azaltma
- NEG - negatif
Bazılarını bu yazıda pratikte göreceğiz. Diğerleri sonraki yazılarda ele alınacaktır.
Genellikle programlama dilleri değerlendirme sırasını değiştirme yeteneğine sahiptir (if deyimi, case statement, goto vb) ve assembly de de vardır.Burada bazılarını göreceğiz. İki değer arasında karşılaştırma yapmak için cmp komutu vardır.Karar verme için koşullu atlama(conditional jump) komutu ile birlikte kullanılır. Örneğin:
;50 ile rax'ı karşılaştır
cmp rax, 50
cmp komutu sadece 2 değeri karşılaştırır, ancak bunları etkilemez ve karşılaştırma sonucuna bağlı olarak hiçbir şey çalıştırmaz.Karşılaştırmadan sonra herhangi bir işlem yapmak için koşullu atlama yönergeleri vardır. Bunlardan biri olabilir:
- JE - eşitse
- JZ - sıfır ise
- JNE - eşit değilse
- JNZ - sıfır değilse
- JG - ilk operand(işlenen) ikinci işlenenden büyük ise
- JGE - ilk operand(işlenen) ikinci işlenenden büyük veya eşit ise
- JA - JG ile aynı ancak işaretsizler için karşılaştırma yapar
- JAE - JGE ile aynı ancak işaretsizsizler için karşılaştırma yapar
Örneğin, C dilinde if/else ifadesi gibi bir şey yapmak istiyorsak:
if (rax != 50) {
cikis();
} else {
dogru();
}
Assembly dilinde olsaydı:
;50 ile rax'ı karşılaştır
cmp rax, 50
;Eğer rax 50'ye eşit değilse .cikis'i gerçekleştir.
jne .cikis
jmp .dogru
Sözdizimi ile koşulsuz atlama da vardır:
JMP label
Örneğin:
_start:
;; ....
;;Bir şeyler yap ve .cikis etiketine atla
;; ....
jmp .cikis
.cikis:
mov rax, 60
mov rdi, 0
syscall
Burada _start etiketinden sonra olacak bir kodumuz olabilir ve bu kodun tamamı çalıştırılır, Assembly kontrolu .cikis etiketine transfer eder ve kod .cikis'dan sonra çalışmaya başlayacaktır.
Genellikle koşulsuz atlama döngüleri kullanır. Örneğin, etiketimiz(label) ve ondan sonra bir kodumuz var.Bu kod koşul durumdan başka her şeyi çalıştırır ve koşul başarılı olmazsa bu kodun başlangıcına atlar.Döngüler sonraki bölümlerde ele alınacaktır.
Basit bir örnek görelim.İki tam sayı alır, bu sayıların toplamını alır ve önceden tanımlanmış bir sayıyla karşılaştırır.Eğer önceden tanımlanmış sayı, toplama eşitse ekrana bir bir şey yazdırılır eğer değilse sadece programdan çıkar.İşte örneğimizin kaynak kodu:
section .data
; Sabitleri tanımla
num1: equ 100
num2: equ 50
; mesajı ata
msg: db "Toplam doğru\n"
section .text
global _start
;; giriş noktası
_start:
; num1 değerini rax ‘a ata
mov rax, num1
; num2 değerini rbx‘a ata
mov rbx, num2
; rax ve rbx toplamını al ve toplam değerini rax'da sakla.
add rax, rbx
; rax ve 150'yi karşılaştır.
cmp rax, 150
; rax ve 150 eşit değilse .cikis etiketine git
jne .cikis
; rax ve 150 eşitse .dogruToplam etiketine git
jmp .dogruToplam
; Toplam doğru ise mesajı yazdır
.dogruToplam:
; write syscall(sistem çağrısı)
mov rax, 1
; file descritor(dosya tanıtıcı), standart output(çıktı)
mov rdi, 1
; mesajın adresi
mov rsi, msg
; mesajın uzunluğu
mov rdx, 13
; write syscall(sistem çağrısı) çağır
syscall
; programdan çık
jmp .cikis
; çıkış prosedürü
.cikis:
; exit syscall
mov rax, 60
; çıkış kodu
mov rdi, 0
; exit syscall(sistem çağrısı) çağır
syscall
Kaynak kodu üzerinden gidelim. İlk olarak, iki sabit olan num1, num2 ve "Toplam doğru\n" değerine sahip msg değişkenini içeren veri bölümü vardır.Şimdi 14'üncü satıra bakalım.Burası programın giriş noktasıdır.num1 ve num2 değerlerini genel amaçlı registerlar olan rax ve rbx'e aktarırız.add komutu ile toplarız.add komutunun çalıştırılmasından sonra rax ve rbx'den değerlerin toplamını hesaplar ve değeri rax da saklarız.Şimdi rax registerında num1 ve num2'nin toplamı var.
Tamam, değeri 100 olan num1 ve değeri 50 olan num2'ye sahibiz.Toplamımız 150 olmalı.cmp komutuyla kontrol edelim.rax ile 150 değerini karşılaştırdıktan sonra karşılaştırma sonucunu kontrol ediyoruz eğer rax 150'ye eşit değilse (jne ile kontrol ederek) .cikis etiketine gidiyoruz eğer eşitse .dogruToplam etiketine gidiyoruz.
Şimdi iki etiketimiz var: .cikis ve .dogruToplam.Birinci etiket, rax için 60'a( çıkış için sistem çağrı numarasıdır) ve rdi için 0'a atama yapar.(bu bir çıkış kodudur.)İkinci etiket ise .dogruToplam'dır.Oldukça basittir sadece "Toplam doğru" yazdırır.