Aramızda birçok geliştirici var.Her gün bir ton kod yazıyoruz hatta bazen güzel kod bile yazdığımız oluyor :) Her birimiz kolayca bu gibi en basit kodu yazabiliriz:
#include <stdio.h>
int main() {
int x = 10;
int y = 100;
printf("x + y = %d", x + y);
return 0;
}
Her birimiz bu C kodunun ne yaptığını anlayabiliriz.Ama… Bu kod nasıl düşük seviyede(low level) çalışıyor? Sanırım hepimiz bu soruya cevap veremeyiz, ben de. Haskell, Erlang, Go ve benzeri gibi üst düzey programlama dillerinde kod yazabileceğimi düşündüm ama derlemeden sonra nasıl düşük seviyede çalıştığını kesinlikle bilmiyorum.Bu yüzden assembly'e birkaç derin adım atmaya ve bu konudaki öğrenme yolumu ifade etmeye karar verdim. Umarım sadece benim için ilginç olmaz.Yaklaşık 5 - 6 yıl önce zaten basit assembly programları yazıyordum.Üniversitedeydim, Turbo assembly ve DOS işletim sistemini kullandım. Şimdi Linux-x86-64 işletim sistemini kullanıyorum. Evet, Linux 64 bit ve DOS 16 bit arasında büyük bir fark olmalı. Haydi başlayalım.
Başlamadan önce, yazdığım gibi bazı şeyleri hazırlamak zorundayız,Ubuntu kullanıyorum (Ubuntu 14.04.1 LTS 64 bit) bu yüzden gönderilerim bu işletim sistemi ve mimarisi için olacak.Farklı CPU, farklı komut setini destekler.Intel Core i7 870 işlemcisini kullanıyorum ve tüm kodlar bu işlemci için yazılacak.Ayrıca nasm assembly kullanacağım.Şu şekilde yükleyebilirsiniz:
$ sudo apt-get install nasm
Sürüm 2.0.0 veya üstü olmalıdır. 29 Aralık 2013 sürümünde derlenen NASM sürüm 2.10.09 kullanıyorum.Ve son kısım, assembly kodunu yazacağınız metin editörüne ihtiyacınız olacak.Bunun için Emacs'i nasm-mode.el ile kullanıyorum. Bu zorunlu değildir elbette en sevdiğiniz metin editörünü kullanabilirsiniz.Emacs'i benim gibi kullanacaksanız, nasm-mode.el dosyasını indirebilir ve Emac'lerinizi bu şekilde yapılandırabilirsiniz:
(load "~/.emacs.d/lisp/nasm.el")
(require 'nasm-mode)
(add-to-list 'auto-mode-alist '("\\.\\(asm\\|s\\)$" . nasm-mode))
Bu an için ihtiyacımız olan tek şey bu.Diğer araçlar sonraki yazılarda açıklanacaktır.
Burada assembly dilinin bütün sözdizimini açıklamayacağım, yalnızca bu yazıda kullanacağımız sözdiziminin bölümlerinden bahsedeceğiz.Genellikle NASM programı bölümlere ayrılmıştır. Bu yazıda, aşağıdaki 2 bölümle karşılaşacağız:
• data section (veri bölümü)
• text section (metin bölümü)
Data section, sabitleri bildirmek için kullanılır.Bu veriler çalışma zamanında(runtime) değişmez.Çeşitli matematiksel veya diğer sabitleri vb. bildirebilirsiniz. Data section, için sözdizimi şu şekildedir:
section .data
Text section, kod içindir.Bu bölüm, program yürütmesinin başladığını çekirdeğe söyleyen global _start bildirgesiyle başlamalıdır.
section .text
global _start
_start:
Yorumlar ; sembolü ile başlar.Her NASM kaynak kodu satırı, aşağıdaki dört alanın bazı kombinasyonlarını içerir:
[label:] komut [operand] [; yorum]
Köşeli parantez içindeki alanlar isteğe bağlıdır. Temel bir NASM talimatı iki bölümden oluşur.Birincisi, yürütülecek komutun adı ve ikincisi bu komutun operand(işlenen)'larıdır. Örneğin:
MOV COUNT, 48 ; COUNT değişkenine 48 değerini koy
NASM assembly ile ilk programı yazalım. Ve tabiki geleneksel Merhaba dünya programı olacak. İşte bunun kodu:
section .data
msg db "Merhaba, dünya!"
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, 16
syscall
mov rax, 60
mov rdi, 0
syscall
Evet, printf(“Merhaba dünya”) gibi görünmüyor. Ne olduğunu ve nasıl çalıştığını anlamaya çalışalım.1'inci ve 2'inci satıra bakın.Veri bölümünü(data section) tanımladık ve oraya msg sabitiyle Merhaba dünya değerini koyduk.Şimdi bu sabiti kodumuzda kullanabiliriz.Sonraki bildirim text section’dir ve programın giriş noktasıdır. Program 7'inci satırdan çalışmaya başlayacaktır.Şimdi en ilginç kısım başlıyor.Mov komutunun ne olduğunu zaten biliyoruz, 2 işlenen(operand) oluyor ve ikincisini ilk değerine koyuyoruz.Ama bu rax, rdi ve vs… nedir? Wikipedia'da okuyabildiğimiz gibi:
Bir merkezi işlem birimi (CPU), sistemin temel aritmetik, mantıksal ve giriş/çıkış işlemlerini
gerçekleştirerek bir bilgisayar programının yönergelerini yerine getiren bir bilgisayardaki donanımdır.
Tamam, CPU bazı aritmetik ve benzeri işlemleri gerçekleştiriyor…Ama bu işlemler için veriyi nereden bulabilir? İlk cevap bellek.Bununla birlikte verileri ramdan okumak ve verileri ram'e depolamak, kontrol bus'ı boyunca veri gönderme isteği sürecinin karmaşık olmasından dolayı işlemciyi yavaşlatır.Böylece CPU'nun register(yazmaç) adı verilen kendi dahili bellek depolama yerleri vardır:
Yani mov rax, 1 yazdığımızda bunun anlamı rax register'ına 1 koy demektir.Şimdi rax, rdi, rbx ve diğerlerinin ne olduğunu biliyoruz… Fakat rax'ı ne zaman kullanacağımızı bilmeliyiz ama rsi ve vb…
• rax - geçici register; Bir syscall çağırdığımızda, rax syscall numarası içermelidir
• rdx - 3. argümanı fonksiyonlara aktarmak için kullanılır
• rdi - 1. argümanı fonksiyonlara aktarmak için kullanılır
• rsi - fonksiyonlara 2. argümanı iletmek için kullanılan işaretçi(pointer)
Başka bir deyişle, sadece sys_write syscall çağrısı yapıyoruz. sys_write'e bir göz atın:
size_t sys_write(unsigned int fd, const char * buf, size_t count);
3 argümanı var:
• fd - file descriptor(dosya tanıtıcısı). Standart giriş, standart çıkış ve standart hata için 0, 1 ve 2 olabilir.
• buf - Bir karakter dizisine işaret eder. fd ile işaret edilen dosyadan elde edilen içeriği saklamak için kullanılabilir.
• count - dosyadan karakter dizisine yazılacak bayt sayısını belirtir.
Yani sys_write syscall'ın üç argüman aldığını ve syscall tablosunda bir numaraya sahip olduğunu biliyoruz. Merhaba dünya uygulamamıza tekrar bakalım.rax register'ına 1 koyduk, bu da sys_write sistem çağrısını kullanacağımız anlamına geliyor.Bir sonraki satırda rdi register'ına 1 koyduk, sys_write'ın ilk argümanı olacak, 1 - standart çıktı.
Sonra msg işaretçisini rsi registerında saklıyoruz, sys_write için ikinci buf argümanı olacak.Ve sonra son (üçüncü) parametreyi (stringin uzunluğunu) rdx'e geçiririz, sys_write'in üçüncü argümanı olacaktır.Şimdi sys_write'in tüm argümanlarına sahibiz ve bunu 11'inci satırda syscall fonksiyonu ile çağırabiliriz.
Tamam, “Merhaba dünya” stringini yazdırdık şimdi programdan doğru bir şekilde çıkmamız gerekiyor.rax registerına 60 atıyoruz.60, bir exit syscall sayısıdır.Ve ayrıca rdi registerına 0 geçiyoruz, hata kodu olacak, 0 ile programımız başarılı bir şekilde çıkmalı.Hepsi “Merhaba dünya” için. Oldukça basit :)Şimdi programımızı oluşturalım. Örneğin, bu kodumuz merhaba.asm dosyasında bulunsun.O zaman aşağıdaki komutları yürütmeliyiz:
$ nasm -f elf64 -o merhaba.o merhaba.asm
$ ld -o merhaba merhaba.o
Bundan sonra ./merhaba ile çalıştırabileceğimiz ve Merhaba dünya stringini terminalde görebileceğimiz,yürütülebilir bir dosyaya sahip olacağız.