Bölüm 6 : Linux İşletim Sistemi Altında Assembly Kullanımı
Bu dokümanda linux’un kullanımı ve sistemin işleyişi hakkında bilgi verilmeyecektir. Bu
bölümde kullanıcının daha önce Linux yada benzeri bir UNIX türevi sistemi kullanmış olduğu
varsayılmaktadır.
Linux işletim sisteminin çalışabileceği en eski CPU modeli 80386 mimarisidir. Yani linux
8086, 80186 yada 80286 kullanan bir bilgisayar üzerine kurulamaz. Intel firmasının 80386 ile
getirdiği en büyük yenilik, eski modellerde (8086, 80186 yada 80286) kullanılan 16-bit
register’lar yerine 32-bit register’ların kullanımına olanak sağlamasıdır. Bu da programcıya
daha çok adreslenebilir bellek alanı sağlamaktadır. 80386’dan önceki modellerde bir segment
64KB boyutunda olurken bu rakam 80386 ve sonrasında 4GB’dan daha fazladır. (Bu dokümanı
yazdığım sıralarda Intel firması yeni 64-bit kapasiteli işlemciler üzerinde çalışıyordu!)
80386’da, daha önce kullanılan register’lara ek yapılmış ve bazı yeni register’lar eklenmiştir.
Her biri 16-bit olan AX, BX, CX, DX, SI, DI, BP ve SP register’ları yerine her biri 32-bit olan,
sırasıyla EAX, EBX, ECX, EDX, ESI, EDI, EBP ve ESP register’ları kullanılmaktadır. Daha
önceki işlemcilerde kullanılan 16-bit segment register’ları olan CS, DS, ES ve SS’e ek olarak
iki 16-bit’lik segment register’ı olan FS ve GS eklenmiştir. 80386’da önceki modeller gibi 16-
bit’lik segment register’ları kullanmaktadır. Tüm bunlara ek olarak flag register da 32-bit
kapasiteye sahip olmuştur. Genişletilmiş yeni flag register’ın 16. biti “debug resume”(RF) ve
17. biti “virtual mode” (VM) bitleri olarak adlandırılırlar. İşlemci sanal mod (virtual mode)
altında çalışırken 8086 mimarisi ile işlem yapar. Bunalara ek olarak, 80486’da “alignment
check flag” biti 18. bit pozisyonuna eklenmiştir.
32-bit programlar yazılırken 16-bit’lik ve 8-bit’lik register’lar kullanılabilmektedir.
80386’nın kullandığı register şeması aşağıdaki gibidir.
45
Linux altında yazacağımız programların hepsi 80386’nin sağladığı 32-bit kapasiteli
register’ları kullanabilmektedir. Linux altında assembly dili kullanımı programcıya DOS
altındaki kadar büyük avantajlar sağlamamaktadır. Çalıştırılan programlar korumalı mod
(protected mode) altında çalıştığı için programcının BIOS tarafından sağlanan kaynaklara
erişimi kısıtlanmıştır. (Aslında bu korumalı modda çalışan tüm sistemler için geçerlidir)
Linux altında kullanılacak assembly rutinleri çoğunlukla C gibi diller içerisinde satır içi
(inline) olarak kullanılmaktadır. Yazılan programların DOS altında yüksek seviyeli bir dil ile
ve assmbly ile yazılmış iki program arasındaki kadar büyük bir boyut farkı göstermemesi ve
korumalı moddan dolayı kullanımdaki kısıtlamalar dilin Linux (yada herhangi bir UNIX türevi
sistem) altındaki popülaritesini azaltmıştır. Fakat yine de bazı noktalarda başka alternatif
bulunmamakta ve assembly dilinin kullanımı zorunlu olmaktadır. (Linux Kernel Ver1.0
kaynağındaki boot dosyaları, http://www.kernel.org)
Linux altında kaydedilen assembly dosyaları .s yada .S uzantısını alır. Program kullanılan
assembler programına göre AT&T yada Intel sözdizimi kullanılarak yazılır. Eğer assembler
olarak linux ve DOS altında kullanılan NASM (Netwide Assembler) kullanılacaksa yazılan
program Intel sözdizimi kullanılarak yazılır. Dokümanda buraya kadar anlatılan assembly dili
kuralları Intel sözdizimi içindir. Buna karşın linux altında sıkça kullanılan debugger
programlarından birisi olan GDB ile yazılacak kodlarda, C ile kullanılacak satır içi kodlarda ve
linux çekirdeğindeki kodlarda hep AT&T sözdizimi kullanılmaktadır. Aslında GCC derleme
sırasında “as” adında bir assembler programı kullanır. “as”, AT&T sözdizimini kullandığı için
GCC ile derlenen C dosyalarındaki kodlar da mecburen AT&T sözdizimi ile yazılmalıdır.
Bu dokümanda temel olarak Intel ve AT&T sözdizimleri arasıdaki farklar üzerinde
durulacaktır ve GNU Assembler kullanımına bazı örnekler verilecektir.
6.1 Intel ve AT&T Sözdizimleri
Yukarıda da belirttiğim gibi linux altında assembly programları yazılırken kullanılan
sözdizimi AT&T standartlarındadır. DOS altında kullanılan assembler programları Intel
sözdizimi ile yazılmış kodları birleştirirken, linux (ve UNIX türevi sistemlerde) kullanılanlar,
bir iki istisna dışında AT&T sözdizimini şart koşmaktadır.
46
Intel AT&T
mov eax, 01h
mov bx,1234h
movl $0x01, %eax
movw $0x1234, %bx
Yukarıda da görüldüğü gibi AT&T sözdizimi Intel’inkine göre biraz daha karmaşıktır. Şimdi
isterseniz sıra ile farkları inceleyelim.
6.1.1 Kaynak-Hedef Yönü
Intel sözdiziminde genel form “komut hedef, kaynak” olmasına karşın AT&T’de bu dizilim
“komut kaynak, hedef” şeklindedir. Aslında, göze normal gelen de değerin soldan sağa
taşınmasıdır.
Intel AT&T
komut hedef, kaynak
mov ebp ,esp
komut kaynak, hedef
movl %esp, %ebp
6.1.2 Önekler
AT&T sözdiziminde register’lar “%” ve sabit değerler de “$” işareti ile öneklerini alır. Sabit
değer olarak kullanılan sayı onaltılık sistemde yazılıyor ise “0xsayı” gibi bir ifade kullanılır.
Intel AT&T
mov ah, 12h movb $0x12, %ah
Yukarıdaki örnekte AH içerisine 12h (onluk 18) değeri atanmıştır. AT&T sözdiziminde sabit
değerin başında “$” işareti olduğuna ve 12h değerini temsil etmek için 0x12 yazım biçimini
kullandığımıza dikkat edin. “movb $0x12, %ah” ile “movb $12, %ah” arasında çok fark vardır.
İkinci komutun GDB içerisindeki karşılığı “movb $0xC, %ah” şeklinde olacaktır.
6.1.3 Sonekler
Sözdizimleri arasındaki bir diğer fark da AT&T sözdiziminde komutların sonuna konulan “l”,
“w” ve “b” sonekleridir. Bu sonekler üzerinde işlem yapılan bilginin boyu hakkında bilgi
vermek için kullanılır. Intel sözdiziminde kullanılan “dword ptr”, “word ptr” ve “byte ptr”
eklerine karşılık gelmektedirler.
32-bit veri işlemleri için “l” (long), 16-bit veri işlemleri için “w” (word) ve 8-bit veri
işlemeleri için “b” (byte) sonekleri kullanılır.
Intel AT&T
mov byte ptr bh, 45h
mov eax, dword ptr [ebx]
movb $0x45, %bh
movl (%ebx), %eax
47
6.1.4 Bellek İşlemleri
80386’nın yeni register’ların yanı sıra programcılara sağladığı en büyük yenilik bellek
adresleme metotlarındadır. Bu bölümü okumadan önce 8086 serisinde bellek adresleme
konusunda bilgi sahibi olmanız gerekmektedir.
8086 serisinde dolaylı adresleme yapılırken sadece BX register’ı kullanılırken, 80386 ve
sonrası işlemcilerde herhangi bir genel amaçlı (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP)
register kullanılabilmektedir. Yani aşağıdaki yazım 80386 öncesi bir işlemcide çalışmazken
80386 ve sonrasında sorunsuz çalışmaktadır.
mov al, [edx]
Dikkat edilmesi gereken bir nokta da 80386 işlemcide programınızı 16-bit gerçek modda
(real mode) çalıştırıyorsanız yine adresleyebileceğiniz belek bölgesi 64KB boyundadır ve
erişim sırasında 32-bit register’ları kullanmanız gerekmektedir.
80386 herhangi bir register’ı base yada index adresleme için kullanmanıza olanak sağlar.
Tek kısıtlama ESP’yi bir index değer olarak kullanamayacağınızdır.
Bunların yanı sıra 80386’da “index” değeri 1, 2, 4 veya 8 gibi sabitlerden birisi ile çarpılıp
daha etkin bir bellek erişimi sağlanabilmektedir. Genel Intel ve AT&T sözdizimi şekilleri
aşağıda verilmiştir.
INTEL komut hedef, segment_register:[base + index * n + yer_değiştirme]
AT&T komut %segment_register:yer_değiştirme(base, index, n ), hedef
( n = 1, 2, 4 veya 8 )
Aşağıdaki örnekler, yukarıdaki formalrın kafanızda oluşturabileceği soru işaretlerini silecektir
sanırım.
Intel AT&T
mov eax, [ecx*8h]
mov edx, [ebx+ecx*08h-05h]
movl 0x08(%ecx), %eax
movl –0x05(%ebx,%ecx,0x08)
Bu noktada AT&T sözdizimi Intel’inki ile kıyaslandığında, gerçekten hiç de programcı dostu
değil!
80386 ile yazılan kodlarda bellek adresleme yapılırken kullanılan register’lardan ilkinin
“base” ve ikincisinin “index” olduğu varsayılır. Programcı bu noktada dikkatli olmalıdır.
Çünkü işlemci bilgiyi alacağı segmenti kullanılan “base” register’a göre belirler. Sadece base
olarak ESP yada EBP kullanıldığı zaman geçerli segment Stack Segment’tir. Diğer durumlarda
varsayılan olarak Data Segment kullanılır.
6.1.5 INT 0x80 ve Linux Sistem Çağrıları (Syscalls)
Linux altında assembly programları yazılırken çok kullanılan rutinlerden bazıları da sistem
çağrılarıdır. Herhangi bir sitem çağrısının kullanılabilmesi için, sistem çağrısına ait numara
EAX içerisine atılır ve eğer sistem çağrısı en fazla beş agrüman alıyorsa gerekli argümanlar
sırası ile EBX, ECX, EDX, ESI ve EDI register’larına atılır. Altı ve daha fazla argüman alan
48
sistem çağrılarında ise yine sistem çağrı numarası EAX içerisine atılır. Daha sonra gerekli
agrümanlar sondan başa doğru stack içerisine atılır ve ESP EBX içerisine kopyalanır. Sistem
çağrıları /usr/include/asm/unistd.h içerisinde listelenmiştir. Ayrıca sistem çağrıları hakkında
bilgiyi de “man 2 sistem_çağrısının_adı” komutu ile alabilirsiniz.
Örnekler :
ilk.s
fnoyan@tux:~$ cd asm
fnoyan@tux:~/asm$ vi ilk.s
“ilk.s” dosyası içerisine aşağıdaki kodları yazın.
.text
.globl _start
_start:
movl $12,%eax
movl $0x12, %eax
xorl %eax, %eax
inc %eax
int $0x80
fnoyan@tux:~/asm$ as ilk.s –o ilk.obj
fnoyan@tux:~/asm$ ld ilk.obj –o ilk
fnoyan@tux:~/asm$ ./ilk
fnoyan@tux:~/asm$
fnoyan@tux:~/asm$ gdb -q
(gdb) file ilk
Reading symbols from ilk...(no debugging symbols found)...done.
(gdb) disas _start
Dump of assembler code for function _start:
0x8048074 <_start>: mov $0xc,%eax
0x8048079 <_start+5>: mov $0x12,%eax
0x804807e <_start+10>: xor %eax,%eax
0x8048080 <_start+12>: inc %eax
0x8048081 <_start+13>: int $0x80
End of assembler dump.
(gdb)
Yukarıda ilk.s dosyasının adım adım birleştirilmesi ve GDB yardımı ile içerisindeki assembly
kodlarını incelenmesi gösterilmiştir. Örneğimizde, GDB ile kullanılan “file” komutu debug
49
içerisindeki “n” komutu ile ve “disas” komutu da debug içerisindeki “u” komutu ile aynı işi
yapmaktadır.
Bu örnekte yukarıda verdiğim “movl $12,%eax” ve “movl $0x12, %eax” komutları arasındaki
farkı rahat bir şekilde görebilirsiniz.
“disas _start” komutu ile GDB programımız içerisindeki “_start” noktasından itibaren dağıtma
işlemi yapar.
iki.s
Bu örneğimizde bir sistem çağrısı olan “chmod”un kullanımı örneklendirilmiştir. Sistem çağrı
numarasını (chmod için 15) /usr/include/asm/unistd.h dosyasından öğrendikten sonra gerekli
agrümanları linux yardım dosyalarını okuyarak öğrenebilirsiniz.
fnoyan@tux:~$ man 2 chmod
Gerekli argümanlar öğrenildikten sonra aşağıdaki kodu iki.s adı kaydedin.
.data
path: .ascii "./dosya"
.text
.globl _start
_start:
movl $15, %eax
movl $path, %ebx
xorl %ecx, %ecx
int $0x80
xorl %eax, %eax
incl %eax
int $0x80
Yukarıdaki program, bulunduğu dizindeki “dosya” isimli dosyanın erişim haklarını
sıfırlamaktadır. Yani programımız “chmod 000 ./dosya” komutu ile aynı işi yapmaktadır.
fnoyan@tux:~/asm$ touch dosya; chmod 777 dosya
fnoyan@tux:~/asm$ ls –l dosya
fnoyan@tux:~/asm$ as iki.s –o iki.obj
fnoyan@tux:~/asm$ ld iki.obj –o iki
fnoyan@tux:~/asm$ ./iki
fnoyan@tux:~/asm$ ls –l dosya
....
....
fnoyan@tux:~/asm$ gdb -q
(gdb) file iki
Reading symbols from iki...(no debugging symbols found)...done.
50
(gdb) disas _start
Dump of assembler code for function _start:
0x8048074 <_start>: mov $0xf,%eax
0x8048079 <_start+5>: mov $0x8049088,%ebx
0x804807e <_start+10>: xor %ecx,%ecx
0x8048080 <_start+12>: int $0x80
0x8048082 <_start+14>: xor %eax,%eax
0x8048084 <_start+16>: inc %eax
0x8048085 <_start+17>: int $0x80
End of assembler dump.
(gdb)
“dosya” isimli bir dosya oluşturduk ve erişim haklarını –rwxrwxrwx (777) olarak düzenledik.
Daha sonra yazdığımız program ile “dosya” isimli dosyanın erişim haklarını ----------(000) olarak
değiştirdik.
“man 2 chmod”a göre fonksiyon iki argüman alıyor. Birincisi dosyaya giden yol (path) ve
ikincisi yeni erişim yetkileri (bizim örneğimizde sıfır). İsterseniz adım adım programı inceleyelim.
.data
path: .ascii "./dosya"
Yukarıdaki parçada program içerisinde ascii türünde sabit tanımlaması yapılıyor. “.data” Data
Segment içerisinde olduğumuzu belirtir.
.text
.globl _start
_start:
Code Segment’e geçiliyor.
movl $15, %eax
movl $path, %ebx
xorl %ecx, %ecx
int $0x80
İşte burada chmod sistem çağrısı çağırılıyor. EAX içersine sistem çağrı numarası atıldıktan
sonra gerekli argümanlar register’lara yükleniyor. Burada “movl $path, %ebx” komutu ile yapılan
sabitimizin adresini EBX içerisine yüklemektir (programın GDB çıktısında bu görülmektedir.).
Daha sonra ECX içerisine sıfır atanıyor ve en son olarak “int $0x80” çalıştırılıyor.
51
xorl %eax, %eax
incl %eax
int $0x80
Programımızın sonunda da, programı bitirmek için _exit() sistem çağrısı (sistem çağrı numarası
1) kullanılıyor. Bu DOS altındaki AH=4Ch INT 21h çağrısı ile aynı işi yapmaktadır.
uc.s
Son örneğimizde de yukarıda oluşturduğumuz “dosya” isimli dosyanın adını “yeni_dosya”
olarak değiştiriyoruz. Bu işe için yine bir sistem çağrısını kullandık: rename (sistem çağrı
numarası 38).
Kodumuz aşağıdaki gibi.
.data
old_path: .ascii "./dosya\0"
new_path: .ascii "./dosya_yeni\0"
.text
.globl _start
_start:
movl $38, %eax
movl $old_path, %ebx
xorl $new_path, %ecx
int $0x80
xorl %eax, %eax
incl %eax
int $0x80
Yukarıda da yine bir önceki örnekte olduğu gibi sistem çağrısı kullanıldı. Bu sefer de sistem
çağrı numarasına ek olarak iki argüman gerekti ve ilki EBX ikincisi de ECX içerisine yazıldı.
“movl $old_path, %ebx” komutuyla belirtilen “old_path” sabiti derlenme sırasında gerekli offset
adresi ile değiştirilmektedir. Aşağıdaki GDB çıktısı bunu daha kolay anlamanızı sağlayacaktır.
fnoyan@tux:~/asm$ gdb -q
(gdb) file uc
Reading symbols from uc...(no debugging symbols found)...done.
(gdb) disas _start
Dump of assembler code for function _start:
0x8048074 <_start>: mov $0x26,%eax
0x8048079 <_start+5>: mov $0x804908c,%ebx
0x804807e <_start+10>: xor $0x8049094,%ecx
0x8048084 <_start+16>: int $0x80
0x8048086 <_start+18>: xor %eax,%eax
52
0x8048088 <_start+20>: inc %eax
0x8048089 <_start+21>: int $0x80
End of assembler dump.
GDB ile görüntülenen kodda komutların soneklerinin olmadığına dikkat ediniz.
İnternet Adresleri :
Dokümanın bu son kısmında assembly ile daha geniş çapta uğraşmak isteyenler için bazı
internet sitelerinin adresleri bulunmaktadır.
http://linuxassembly.org Linux ve diğer UNIX tabanlı sistemler altında
assembly dili ve kullanımı hakkında kaynak
bulabileceğiniz bir site.
http://www.janw.easynet.be/eng.html Linux altında assembly dilinin kullanımını anlatan
bir başka yazı.
http://www.geocities.com/SiliconValley
/Ridge/2544/
Linux altında GCC ile satır içi assembly kullanımı
hakkında bilgi bulabileceğiniz bir adres.
ftp://www6.software.ibm.com/software/
developer/library/l-ia.pdf
GCC ile satır içi assembly hakkında başka bir
doküman.
http://members.lycos.co.uk/kasiasyg/ Adresleri yukarıda verilen bazı dokümanların Türkçe
çevirilerinin bulunduğu bir site.
http://www.programmersheaven.com Sadece assembly değil daha birçok programlama dili
hakkında örnek kod ve kaynak bulabileceğiniz geniş
içerikli bir site.
http://www.web-sites.co.uk/nasm/ Linux ve MS-Windows işletim sistemleri altında
kullanılan bir assembler olan NASM’ın sitesi.
http://win32asm.cjb.net Windows işletim sistemi altında 32-bit assembly
programlamayı öğrenebileceğiniz en iyi sitelerden biri.
Yukarıdakilerin dışında adreslerini bilmediğim ama sizin bir arama motorunda gerekli bilgileri
yazarak kolayca erişebileceğiniz bazı faydalı kaynaklar.
The Art Of Assembly Language
PC Assembly Language Yazar : Paul.A Carter
Ralf Brown’s Interrupt List Yazar : Ralf Brown
Hiç yorum yok:
Yorum Gönder