C ile Standart Kütüphane olan LIBC’yi Kullanmadan Program Yazmak

Programlama diline, C,  yerleşik herhangi bir fonksiyonu kullanmadan, sadece programlama dilinin yazım kuralında belirtilen ifadeleri kullanarak, şu makalede belirtildiği gibi, program yazabiliriz.
Bunun nasıl olabildiği konusunda Make vs. Ant – Build araçları ile konfigürasyon yönetimi‘nde ilgili kısma(header dosyaları ve ayrı ayrı derlemeden bahsettiğim kısım) bakılabilir.
Yapılacak programın kaynak kodu, alıntı yaptığım makalede hello.c dosyasının içinde; benim yaptığım uygulamada ise libc_free.c içinde yazılıdır:
int main() { 
 char *str = "Hello World"; 
 return 0; 
}
Bu kod, aşağıdaki gibi derlendiğinde,
jesstess@kid-charlemagne:~/c$ gcc -nostdlib -o hello hello.c
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 00000000004000e8
çalıştırılabilir bir dosya oluşturulabilmektedir.
Bu kodda yapılan şey, bellekte 11 byte’lık bir alan ayırmaktır.
Yukarıda, uyarı olarak, _start sembolü bulunamadı deniyor.
Programı çalıştırdığımızda,
jesstess@kid-charlemagne:~/c$ ./hello
Segmentation fault
ile Segmentation Fault hatası alınıyor.
Bu hatayı düzeltmek için öncelikle derleme işlemi sonunda alınan _start sembolü bulunamadı uyarısına bakalım.
Programın çalışması için gerekli olan bu _start sembolü nedir?
libc içinde miydi acaba, peki libc‘deyse, neresinde tanımlıdır?
Linker açısından, _start sembolü,
programın gerçek giriş noktası(actual entry point)dır.
Yani programın giriş noktası, gerçekte, main değildir.
Bu sembol, bir ELF relocatable‘ı olan crt1.o içindedir.
ctr1.o‘yu link edip _start sembolünün artık bulunabildiğini görelim.
(NOT: Bunu düzeltsek de başka libc startup sembolleri bulunamadı diye yine hata verecek.)
# Kaynak kodu derle ama link etme.
jesstess@kid-charlemagne:~/c$ gcc -Os -c hello.c
# Simdi link etmeyi dene.
jesstess@kid-charlemagne:~/c$ ld /usr/lib/crt1.o -o hello hello.o
/usr/lib/crt1.o: In function `_start':
/build/buildd/glibc-2.9/csu/../sysdeps/x86_64/elf/start.S:106: undefined reference to `__libc_csu_fini'
/build/buildd/glibc-2.9/csu/../sysdeps/x86_64/elf/start.S:107: undefined reference to `__libc_csu_init'
/build/buildd/glibc-2.9/csu/../sysdeps/x86_64/elf/start.S:113: undefined reference to `__libc_start_main'
Buradan, _start sembolünün libc içinde start.S içinde olduğunu anlarız.
(NOT: bendeki mesajlarda start.S belirtilmedi[NEDEN?]:
lifeinbeta@lifeinbeta:~/CSWORKS/C$ ld /usr/lib/crt1.o -o libc_free_hello libc_free.o
/usr/lib/crt1.o: In function `_start':
(.text+0xc): undefined reference to `__libc_csu_fini'
/usr/lib/crt1.o: In function `_start':
(.text+0x11): undefined reference to `__libc_csu_init'
/usr/lib/crt1.o: In function `_start':
(.text+0x1d): undefined reference to `__libc_start_main'
)
start.S dosyasına bakıldığında şu işleri yaptığı görülür:
  1. _start sembolünü export eder.
  2. stack‘i hazırlar.
  3. bazı register‘ları hazırlar
  4. __libc_start_main‘yi çağırır.
csu/libc_start.c (sembol tablosu[start.S]na dönüşecek olan kaynak kod.) nun en altında programımızın main fonksiyonuna çağrı yapıldığı görülür:
/* Nothing fancy, just call the function.  */
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
Peki, bu _start sembolüne niye ihtiyaç duyulmuş?
_start ile main arasında gerçekleşen işlemleri şöyle özetleyebiliriz;
 libc için gerekli olan ayarları yap ve sonra programda yazdığımız main fonksiyonunu çağır.
libc‘yi kullanmadan bu işlemi yapmak için, _start sembolünün yaptığı işi kendimiz yazalım.
Bu durumda libc için gerekli ayarları yapmadan sadece main fonksiyonunu çağırmak yeterli olacaktır:
jesstess@kid-charlemagne:~/c$ cat stubstart.S
.globl _start

_start:
	call main
kaynak kodu aşağıdaki gibi derler ve oluşturduğumuz stub olan stubstart.S assembly dosyası ile birleştirdikten sonra çalıştırırsak:
Assembly stub'ını derlenecek olan dosyanın olusturacagı .o ile link et.
jesstess@kid-charlemagne:~/c$ gcc -nostdlib stubstart.S -o hello hello.c
jesstess@kid-charlemagne:~/c$ ./hello
Segmentation fault
Görüldüğü gibi derleme problemleri yine devam ediyor.
(NOT: Burada Segmentation Fault‘a runtime hatası denmiyor, ‘derleme problemi’ deniyor. )
Ancak, hala segfault alınıyor. Neden? Bunun için, derlenecek dosyayı, derlenmiş dosya debug bilgisini de tutacak şekilde, derleyelim.
Bu şekilde, debug bilgisine, gdb ile bakılabilecek.
Bunu yaparken, main‘e bir breakpoint koyulacak ve segfault‘a kadar adım adım ilerlenecek:
jesstess@kid-charlemagne:~/c$ gcc -g -nostdlib stubstart.S -o hello hello.c
jesstess@kid-charlemagne:~/c$ gdb hello
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu"...
// burada breakpoint konuyor...................
(gdb) break main
Breakpoint 1 at 0x4000f4: file hello.c, line 3.
(gdb) run
Starting program: /home/jesstess/c/hello

Breakpoint 1, main () at hello.c:5
5	  char *str = "Hello World";
(gdb) step
6	  return 0;
(gdb) step
7	}
(gdb) step
0x00000000004000ed in _start ()
(gdb) step
Single stepping until exit from function _start,
which has no line number information.
main () at helloint.c:4
4	{
(gdb) step

Breakpoint 1, main () at helloint.c:5
5	  char *str = "Hello World";
(gdb) step
6	  return 0;
(gdb) step
7	}
(gdb) step

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000001 in ?? ()
(gdb)
Dikkat edilirse, main‘den iki defa geçiliyor, neden? Assembly koduna bakalım:
jesstess@kid-charlemagne:~/c$ objdump -d hello

hello:     file format elf64-x86-64

Disassembly of section .text:

00000000004000e8 <_start>:
  4000e8:	e8 03 00 00 00       	callq  4000f0
  4000ed:	90                   	nop
  4000ee:	90                   	nop
  4000ef:	90                   	nop    

00000000004000f0 :
  4000f0:	55                   	push   %rbp
  4000f1:	48 89 e5             	mov    %rsp,%rbp
  4000f4:	48 c7 45 f8 03 01 40 	movq   $0x400103,-0x8(%rbp)
  4000fb:	00
  4000fc:	b8 00 00 00 00       	mov    $0x0,%eax
  400101:	c9                   	leaveq
  400102:	c3                   	retq
Assembly(ASM) kodunu tamamen incelemeden şuna dikkat edelim.
_start sembolünün belirttiği ASM kodunda, CALLQ ile main fonksiyonu çağrılıyor.
Daha sonra RETQ ile tekrar,  _start‘a dönülüyor.
Stack Frame‘den alınan IP(Instruction Pointer) nin belirttiği adres değeri,  NOP komutunun adresini gösteriyor.
NOP (No Operation) komutları çalıştırılıp geçildikten sonra main fonksiyonunun ASM kodları bir daha çalıştırılıyor.
(Yani, main‘e ikinci defa girmiş olduk)
(NOT: main kodunun başlangıç adresi olan 4000F0 adresi,
_start sembolünün belirttiği bloğun bitiş adresi olan 4000EF‘ nin hemen sonrası)
Bu noktada, bu bloğa ikinci girişimizde return instruction pointer‘ı stack‘e atmadığımız için,
RETQ‘ya bu ikinci gelişimizde mevcut stack frame(stack‘in bir parçası)den, return instruction pointer sanarak o anda alabildiği bir adresi alır. Bu adresi instruction pointer‘a yükler.
IP‘da belirtilen adrese hoplar(jump),
bu adres yanlış adres olduğundan,  segment dışına çıkmış olur ve segfault hatası alır.
Bu olayı biraz açarsam;
Öncelikle buradaki RBP(Register Base Pointer) 64-bitlik sistemlerde olan bir register‘dır. 32-bit sistemlerde bu EBP(Extended Base Pointer)dir.
i386 daki fonksiyon çağırma yöntemine göre, çağrılan fonksiyona dallanılmadan önce,
Base Pointer Register olan EBP ile adresine işaret edilen, bir stack frame oluşturulur.
Fonksiyondan dönünce kullanacağı bilgiler burada saklanır.
Saklanan bilgilerden biri, RIP' in değeridir.
RIP‘ de, dallanılan fonksiyonu çağıran fonksiyona dönüşte çalıştırılacak olan ilk komutun adresi bulunur.
 RIP,  fonksiyona dallanırken otomatik olarak oluşturulan ve RBP ile gösterilen stack frame‘e, otomatik olarak atılır.
Biz ikinci defa main‘e girerken bir fonksiyondan dallanmadığımız halde RETQ ile fonksiyondan dallanıldı sanılarak RIP alındığında, alınan değer yanlış değer olmuş oldu.
(NOT: retq, dönüşte bununla birlikte otomatik olarak atılan bayrakları da alır.)
Kaldığımız yerden devam edersek; bizim ikinci defa main‘e girmeden çıkmamız lazım. Bunun için CALLQ ile dallanılan main‘den dönünce;
bir sistem çağrısı olan SYS_exit enumeration item‘ının değeri “1“,
EAX akümülatör register‘ına yazılmalı.
Ayrıca, SYS_exit‘in tek argümanının, status, değeri olarak “0“, EBX register‘ına yazılır.
Bu şekilde, Kernel‘a temiz bir şekilde(“0” değeri bunu belirtir) şu anda çalıştırılan programdan çıkıldığını bildirmiş olduk.
Bundan sonra, INT $0x80 interrupt‘ı ileKernel‘a geri döneriz.,
lifeinbeta@lifeinbeta:~/CSWORKS/C$ vim stubstart.S
lifeinbeta@lifeinbeta:~/CSWORKS/C$ cat stubstart.S
.globl _start

_start:
	call main
	movl $1, %eax
	xorl %ebx, %ebx
	int $0x80
lifeinbeta@lifeinbeta:~/CSWORKS/C$ gcc -nostdlib stubstart.S -o hello hello.c
gcc: hello.c: No such file or directory
lifeinbeta@lifeinbeta:~/CSWORKS/C$ gcc -nostdlib stubstart.S -o libc_free libc_free.c
lifeinbeta@lifeinbeta:~/CSWORKS/C$ ./libc_free

Görüldüğü gibi, başarıyla derlendi, link edildi ve çalıştırılabilir dosya oluştu. Çalıştırılabilir dosya segfault almadan başarıyla çalıştı.

Güncelleme:15.05.2011 – Linkler

http://www.acm.uiuc.edu/sigmil/RevEng/ch02.html – C ve Java Derleme Süreçleri [Kapsamlı], gcc parametreleri, Assembler, Linking Stage

Güzel bir söz

If you want to fly, you gotta give up the shit that weighs you down!

[Uçmak istiyorsan, seni aşağıya çeken o pislikten kurtulacaksın!]

Advertisements

3 Comments

Filed under compiler, disassembling, gdb, linking, symbol table

3 responses to “C ile Standart Kütüphane olan LIBC’yi Kullanmadan Program Yazmak

  1. Pingback: Header Dosyalarının Yeri ve Search Path Ayarları | Taha Yavuz Bodur weblogging..

  2. Pingback: C – Libc ve Runtime | Taha Yavuz Bodur weblogging..

  3. Pingback: C – Linker ve Nesne Dosyaları | Taha Yavuz Bodur weblogging..

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s