Skip to content

Latest commit

 

History

History
197 lines (175 loc) · 10.5 KB

2.md

File metadata and controls

197 lines (175 loc) · 10.5 KB

X86_64 Assembly'a merhaba deyin [bölüm 2]

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

hackernews hackernews2

Öğ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.

Terminoloji ve Kavramlar

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.

Veri Tipleri

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.

Sections(Bölümler)

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 işlemler

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.

Kontrol Akışı

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.

Örnek

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.