32 Bit İşletim Sistemlerinde Çekirdek Kodlarının

advertisement
32 Bit İşletim Sistemlerinde Çekirdek Kodlarının Derlenmesi
Kaan Aslan
6 Mart 2003
1. Giriş
İşletim sistemi geliştiricilerinin çözmek zorunda olduğu problemlerden biri de
yazılan çekirdek kodun hiçbir yükleme bilgisi içermeyecek biçimde elde edilmesi ve
yüklemeye hazır biçime getirilmesidir. Ticari amaçla oluşturulmuş derleyici ve
bağlayıcı sistemleri genellikle işletim sistemi yazarları için kolaylıklar sunmazlar.
Çünkü ticari sistemlerde amaç yeni bir sistem oluşturmak değil, mevcut sistem
üzerinde uygulamalar ve araçlar geliştirmektir. Yeni bir sistemin oluşturulması çoğu
kez işletim sistemini satan şirketlerin istemediği bir durumdur. Bu nedenle işletim
sistemi geliştirebilmek için gereken uygun araçlar daha çok araştırma kurumları
tarafından ya da bu konuda çalışan kişilerin bireysel gayretleri ile oluşturulmaktadır.
Çekirdek kodların yüklemeye hazır hale getirilmesi için yaygın olarak kullanılan ticari
derleyiciler ve bağlayıcılar kullanışsız kalmaktadır. Bu derleyicilerin kullanıldığı
uygulamalarda amaç kodun ya da çalışabilen kodun istenilen biçime dönüştürülmesi
için ek birtakım araçlara gereksinim duyulur.
Düz ikili dosyaların oluşturulmasında GNU lisansı ile kullanıma sunulan nasm ve
gcc derleyicileri ile ld bağlayıcılarının iyi bir araç olduğu söylenebilir. Pek çok işletim
sistemi projesinde derleme araçları olarak bu programlar kullanılmaktadır.
2. Düz İkili Dosya Nedir?
Bir işletim sisteminde derleme işlemi yapıldığında üretilen amaç kodun belli bir
formatı vardır. Örneğin, Win32 sistemleri COFF formatını, GNU/Linux sistemleri
temel olarak ELF formatını kullanırlar. Amaç kodlar tipik olarak bağlayıcının modülleri
birleştirebilmesi için gereken bilgileri bulundurmaktadır. Bağlayıcı bu modülleri girdi
olarak alır ve işletim sistemine uygun bir çalışabilir kod oluşturur. Bir sistemde
derlenmiş ve çalışabilir duruma getirilmiş olan programlar tipik olarak dört kısma
sahiptir.
2.1. Derleyicilerin Başlangıç Kodu (Start-up Code): Proses başlatıldığında
çalışma derleyici tarafından yerleştirilmiş bir başlangıç kodundan başlar. Bu prosesin
gerçek giriş noktasıdır. Yükleyiciler prosesi yarattıklarında kontrolün aktarılacağı
adresi çalışabilen dosyanın başlık kısmından alırlar. Şüphesiz bu adres bağlayıcı
tarafından amaç koddaki bilgilerden hareketle oluşturulmuştur. Yükleyici çalışabilen
dosyayı yükledikten sonra kontrolü formatta belirtilen adrese geçirir. Bu başlangıç
kodu çeşitli önişlemleri yaparak programlama dilince öngörülen giriş fonksiyonunu
çağıracaktır. Örneğin C’de main fonksiyonu derleyicilerin başlangıç kodları tarafından
çağrılır. Başlangıç kodları aynı zamanda varsayılan çıkış işlemlerini de yapar.
Örneğin tipik olarak C’de akış main fonksiyonunu bitirdiğinde ya da main fonksiyonu
içerisinde return işlemi yapıldığında akış yeniden başlangış koduna dönmekte ve
proses orada sonlandırılmaktadır. Başlangıç kodu temel olarak aşağıdaki gibi bir
yapıya sahiptir:
1
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Derleyicilerin başlangıç kodları genellikle normal bir modül olarak bağlama işlemine
sokulur. Process’in gerçek başlangıç noktası başlangıç kodundaki modülde belirlenir.
Bu modül içerisinde programlama dilinin belirlediği giriş fonksiyonu extern olarak
bildirildiğinden bu fonksiyon bağlayıcı tarafından programı oluşturan diğer modüllerde
otomatik olarak aranacaktır. C’de main fonksiyonunun hiçbir modülde
bulunmamasından dolayı oluşan bağlama hatasının nedeni budur.
Derleyicilerin başlangıç kodları işletim sisteminin çekirdeği için gereksiz ve
zararlıdır. Başlangıç kodlarında derleme işleminin yapıldığı işletim sistemine özgü
pek çok işlemler yapılmaktadır. Buradaki işlemler geliştirilmekte olan işletim sistemi
için bir anlam taşımaz. Neyse ki derleyicinin başlangıç kodundan kurtulmak çok
kolaydır. Tek yapılacak şey bağlama aşamasında giriş kodunun bulunduğu modülü
bağlama işlemine dahil etmemektedir. Düz ikili dosyalarda derleyicilerinin giriş
kodları bulunmamalıdır.
2.2. Çalışabilen Dosyanın Başlık Kısmı: Çalışabilen dosyalar yükleyici için bilgiler
bulunduran başlık kısımlarına sahip olurlar. Başlık kısımlarındaki bilgiler derleme
işleminin yapıldığı sisteme ilişkin oldukları için işletim sistemi geliştiricileri için
gereksiz ve zararlıdırlar. İşletim sisteminin çekirdek kodlarının herhangi bir başlık
kısmı olmamalıdır. Ancak maalesef hemen hemen tüm ticari derleyiciler başlık
kısmını oluşturmak isterler. Başlık kısmı manuel olarak sonradan dosyadan atılabilse
de bu durum ek bir yük oluşturmaktadır. Düz ikili dosyalarda böyle bir başlık kısmı
olmamalıdır.
2.3. Sisteme Özgü Bilgiler: Çalışabilen dosyada derleme işleminin yapıldığı sisteme
özgü olan birtakım özel bölümler oluşturulmuş olabilir. Örneğin Win32 sistemlerinde
import tablosu, export tablosu, kaynak bölümü tamamen Win32 sistemlerine özgü
bölümlerdir. Bu bölümlerin işletim sisteminin çekirdek kodlarında bulunmaması
gerekir. Ancak pek çok derleyici sisteminde bu bölümler default olarak
oluşturulmaktadır. Bu bölmlerin dikkate alınmaması ve dosydan çıkartılması manuel
yöntemlerle mümkün olabilmektedir.
2.4. Gerçek Kod ve Veriler: Nihayet derlenmiş ve bağlanmış olan çalışabilen kod
işlemcinin yürütebileceği kod ve verilere sahiptir. Tipik bir çalışabilen kodda programı
oluşturan kod, statik veri ve yığın alanları vardır. Pek çok sistemde bu alanlara bölüm
(section) denilmektedir. Bölümleri oluşturan sayfalar bellek üzerinde aynı koruma
özellikleriyle biçimlendirilir. Tipik olarak kodun bulunduğu bölüm .text, ilkdeğer
2
Kaan Aslan Makale Arşivi – www.kaanaslan.net
verilmiş statik verilerin bulunduğu bölüm .data, ilkdeğer verilmemiş statik verilerin
bulunduğu bölüm .bss ve yığın bölümü de .stack biçiminde isimlendirilir.
2.5. Özetle...
Düz ikili dosyalar giriş koduna ve başlık kısmına sahip olmayan, sisteme özgü
bölüm içermeyen dosyalardır. Düz ikili dosyalar yalnızca kod ve statik veri içerirler.
Düz ikili dosyalar bellekte bir adrese blok olarak yüklendiğinde problemsiz
çalışabilmelidirler. Bu dosyalarda derleme işleminin yapıldığı sisteme yönelik hiçbir
ek bilgi bulunmamalıdır.
3. Düz İkili Dosyalar Nasıl Elde Edilir?
Yaygın kullanılan ticari derleyici sistemlerinin düz ikili dosya elde edilmesine
yönelik özellikleri yoktur. Bu özlliklere sahip araçlardan en yaygın olanları GNU
lisansıyla yazılmış olan nasm (netwide assembler) sembolik makina dili derleyicisi,
C/C++ derleyicisi ve ld bağlayıcıdır. Bu geliştirme araçları tipik olarak GNU/Linux
sistemlerinde kullanılırlar. Ancak bunların Win32 sistemlerinde çalışan uyarlamaları
da vardır. Fakat maalesef Win32 sistemlerinde çalışan uyarlamalarında düz ikili
dosya oluşturma konusunda problemler gözlenmiştir. Bu nedenle düz ikili dosyaların
bu araçlarla GNU/Linux sistemlerinde derlenmesi iyi bir seçenektir. Tabi bu durum
işletim sisteminin tamamen GNU/Linux sistemlerinde geliştirilmesini zorunlu hale
getirmez. 32 bit işletim sistemlerinin geliştirilmesi başka başka sistemlerde ve başka
derleyicilerle yapılabilir. Ancak düz ikili dosya elde etmek için geliştirme işlemi
bittiğinde bu sistemlerde derleme ve bağlama yapılmalıdır. (Bu durumda kodun
taşınabilir yazılması ve derleyicilere özgü birtakım eklentilerin kullanılmaması gerekir.
Eğer eklentiler kullanılacaksa bunun son derlemenin yapılacağı sisteme uygun
olması anlamlıdır.)
4. Sembolik Makina Dilinde Yazılan Kodların
Düz İkili Formata Dönüştürülmesi
Düz ikili dosya üretebilen en yaygın sembolik makina dili derleyicisi nasm (Netwide
Assembler)’dır. nasm pek çok amaç kodu destekleyen bir sembolik makina dili
derleyicisidir. Bu derleyici Linux a.out, ELF, NetBSD/FreeBSD, COFF ve Microsoft
16-bit OBJ amaç kod formatlarını destekler. GNU lisansıyla yazılmış olan bu
derleyicilerin Linux, DOS ve Win32 uyarlamaları Internet’te pek çok siteden ücret
ödemeksizin indirilebilir (http://nasm.sourceforge.net/). Derleme için gereken komut
satırı argümanlarının yalın biçimi şöyledir:
nasm -f [-o <dosya ismi> ]
-f seçeneği üretilecek amaç kodun formatını belirtir. –o seçeneği ile üretilecek amaç
kodun ismi verilebilir. Eğer –o seçeneği kullanılmazsa dosya isminin sonuna, –f ile
belirtilen dosyanın formatı DOS ya da Win32 sistemlerinde kullanılan formatlardan
biriyse .obj, Unix/Linux sistemlerinde kullanılan bir formatlardan biriyse .o eklenir. İkili
dosyanın üretilmesi durumunda –o belirtmesi yapılmazsa üretilecek dosyaya
herhangi bir uzantı eklenmez. Örneğin:
3
Kaan Aslan Makale Arşivi – www.kaanaslan.net
nasm -f elf x.asm
nasm –f coff y.asm
ilk derlemeden ELF formatında x.o, ikinci derlemeden COFF formatında y.o dosyaları
elde edilir. Seçeneklerden sonra boşluk bırakılmayabilir. Örenğin:
nasm -felf x.asm
Liste dosyası (assembly listing file) almak için –l seçeneği komuta eklenmelidir.
Örneğin:
nasm kernel.asm -l kernel.lst
Düz ikili dosya elde edebilmek için –f bin seçeneği kullanılır. Örneğin:
nasm –f bin kernel.asm –l kernel.lst –o kernel.bin
ile kernel.asm dosyası derlenecek, kernel.lst isimli liste dosyası ile kernel.bin isimli
düz ikili dosya elde edilecektir. -f seçeneği hiç girilmezse default olarak zaten düz ikili
dosya üretilmektedir. Yukarıdaki derleme işlemi ile aşağıdaki tamamen eşdeğerdir:
nasm kernel.asm –l kernel.lst –o kernel.bin
işlemi ile kernel isimli düz ikili dosya elde edilir. nasm derleyicisinin yukarıda
açıkladıklarımızın dışında pek çok seçeneği vardır. Örneğin –u, -e, -a gibi… Bu
seçeneklerle ilgili ayrıntılı bilgileri nasm dokümanlarından elde edebilirsiniz
(http://nasm.sourceforge.net/)
5. C’de Yazılan Kodların Düz İkili Formata Dönüştürülmesi
C’de yazılan kodlar ilk aşamada gcc derleyicisi ile –c seçeneği kullanarak
derlenmelidir. Bilindiği –c seçeneği “yalnızca derle” anlamına gelmektedir. Kaynak
dosya –c seçeneği ile derlendiğinden .o uzantılı amaç dosya oluşturulmuş olur. Bu
amaç dosyadan düz ikili dosyanın elde edilmesi için ld programından faydalanılabilir.
Aşağıdaki C programı üzerinden ilerleyelim:
test.c
int g_x = 10;
void func(void)
{
}
4
Kaan Aslan Makale Arşivi – www.kaanaslan.net
int main(void)
{
func();
return 0;
}
ld bağlayıcısı düz ikili dosya elde etmek için aşağıdaki örnekte belirtildiği gibi
kullanılabilir:
ld test.o –o test.bin –Ttext 0x0 –e main --oformat binary -N
Burada test.o daha önce –c seçeneğiyle derlenmiş olan amaç dosyayı belirtmektedir.
Örneğimizde –o test.bin üretilecek düz ikili dosyayının ismini belirlemek için
kullanılmıştır. Yani işlemden sonra test.bin dosyası üretilecektir. Eğer bu belirleme
yapılmazsa düz ikili dosya a.out ismiyle üretilir. –T seçeneği ile bölümlerin
yerleştirileceği adresler belirlenebilir. Bölüm ismi text, data, bss gibi herhangi bir
bölüm ismi olabilir. Örneğimizde text bölümü sıfır adresinden başlatılmıştır. Bölümün
yeri default olarak hex sistemde belirtilmektedir. Yani yer belirten sayıyı 0x seçeneği
ile başlatmasaydık da yine sayı hex olarak yorumlanacaktı. Bölümlerin yerleri offset
üretiminde etkili olur. Bağlayıcı en düşük bölümü dosyanın başına alarak dizilimi
yapar. Örneğin test.c programı aşağıdaki gibi bağlanmış olsun:
ld test.o –o test.bin –Tdata 0x50 –Ttext 0x100 –e main
--oformat binary -N
Burada düz ikili dosya data bölümü ile başlatılacak ve data bölümünün ilk sembolü
olan 0x50 offsetinde bulunacaktır. Daha sonra dosyaya 0x50 ile 0x10 arasında
boşluk verilecek ve func fonksiyonunun kodu 0x100’den başlatılacaktır. Bölümlerin
düzenlenmesi sonraki bölümde ele alınmaktadır. Çalışabilen kodun başlangıç noktası
–e ile belirlenebilir. Gerçi düz ikili dosyalarda bu belirlemenin bir önemi yoktur. Ancak
bağlayıcının uyarısını kesmek için bu seçenek eklenebilir. Örneğimizde –e main
biçiminde bir belirleme yapılmıştır. (Burada main yerine herhangi bir fonksiyon
olabilirdi.) --oformat seçeneği bağlayıcının üreteceği dosyanın formatını belirlemekte
kullanılır. Düz ikili dosya için,
--oformat binary
seçeneği kullanılmalıdır. Normal olarak ld bağlayıcısı bölümleri sayfa başlarına
hizalar. Yani örneğin, text bölümünü izleyen data bölümü text bölümünün hemen
aşağısında değil bir sayfa (Intel mimarisinde 4K) sonra sayfa başında bulunacaktır.
–N seçeneği bunu engellemek için kullanılmakatadır.
Şimdi test.c dosyasından düz ikili dosyanın elde edilmesini adımlarla yeniden
açıklayalım.
5
Kaan Aslan Makale Arşivi – www.kaanaslan.net
1. Adım:
gcc –c test.c
2. Adım:
ld test.o –o test.bin –Ttext 0x0 –e main --oformat binary –N
işlem sonrasında test.bin dosyası oluşacaktır. Bu dosyayı bir disassembler
programıyla inceleyebilirsiniz. Disassembler olarak nasm ile aynı pakette bulunan
ndisasm programını kullanabilirsiniz:
ndisasm –b32 test.bin
-b32 seçeneği 32 bit modda görüntüleme yapmak için kullanılır. Yukarıdaki programı
disassembler altında şöyle görmelisiniz:
00000000
00000001
00000003
00000005
00000006
00000007
00000009
00000010
00000011
00000013
00000016
0000001B
0000001D
0000001F
00000020
00000022
00000023
00000024
0000002A
00000030
00000032
55
89E5
89EC
5D
C3
89F6
8DBC2700000000
55
89E5
83EC08
E8E5FFFFFF
31C0
EB01
90
89EC
5D
C3
8DB600000000
8DBF00000000
0A00
0000
push ebp
mov ebp,esp
mov esp,ebp
pop ebp
ret
mov esi,esi
lea edi,[edi+0x0]
push ebp
mov ebp,esp
sub esp,byte +0x8
call 0x0
xor eax,eax
jmp short 0x20
nop
mov esp,ebp
pop ebp
ret
lea esi,[esi+0x0]
lea edi,[edi+0x0]
or al,[eax]
add [eax],al
Burada gördüğünüz gibi data bölümünde bulunan g_x global değişkeni –N seçeneği
sayesinde hemen text bölümünden sonraya yerleştirilmiştir. Aynı dosyayı –N
seçeneğini kullanmadan düz ikili formata dönüştürmeyi deneyip karşılaştırma
yapabilirsiniz.
5.1. Bölümlerin Organizasyonu
Çalışabilen dosya tipik olarak text, data, bss ve stack bölümlerinden oluşur.
Fonksiyonların makina kodları text bölümüne, ilkdeğer verilmiş statik ömürlü nesneler
data bölümüne ve ilkdeğer verilmemiş statik ömürlü nesneler ise bss bölümüne
yerleştirilir. stack programın stack alanını belirtmektedir. Örneğin:
6
Kaan Aslan Makale Arşivi – www.kaanaslan.net
int g_a = 10;
int g_b[100];
int main(void)
{
int i;
g_a = 10;
for (i = 0; i < 100; ++i)
g_b[i] = 0;
return 0;
}
gibi bir programda g_a data bölümüne g_b ise bss bölümüne yerleştirilir. main
fonksiyonunun kodu da text bölümünde bulunur. Aksi belirtilmediği sürece bu
bölümler gcc derleyicisi ve ld bağlayıcısı tarafından aşağıdaki sırada
oluşturulmaktadır:
Aslında bss ve stack bölümleri çalışabilen dosyanın içerisinde yer kaplamaz.
Genellikle bu bölümlerin yalnızca uzunlukları çalışabilen dosya formatında tutulur.
Yükleycici dosyayı sanal belleğe yüklediğinde bss ve stack aşağıda (yüksek anlamlı
bölgede) kalmaktadır. bss bölümü yükleme sonrasında sıfırlanmaktadır (ilkdeğer
verilmemiş yerel statik ömürlü nesnelerin içerisinde 0 olduğunu biliyorsunuz). Dosya
sanal belleğe yüklendikten sonra aşağıdaki gibi bir görünüm elde edilir:
7
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Görüldüğü gibi heap alanı bss alanının hemen altından başlatılıp aşağıya doğru
büyümektedir. Böylelikle bss ve stack alanlarının çalışabilen dosya içerisinde yer
kaplaması engellenmiş olur.
Düz ikili dosya üretilirken aksi belirtilmediği sürece yukarıdaki sıraya uygun kod
üretilir. bss bölümünün sonda bırakılması düz ikili dosyada yer kaplamasını
önlemektedir. Ancak tabi bu bölümdeki nesneler için offset sanki bu bölüm varmış
gibi üretilir.
8
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Download