Category Archives: linking

Header Dosyalarının Yerleri ve Search Path Ayarları


Bu yazıda şu sorular üzerinden gideceğim:

  • C’de Header dosyalarının varsayılan yerleri neresidir. Kullanıcının header dosyaları nerelerde olabilir?
  • Header dosyaları nasıl bulunur?
  • Header dosyasının belirttiği object file nasıl bulunur?
  • Object File‘ların oluşturduğu kütüphaneler(.so, .dll) nasıl bulunur ve burada gerekli olan bir bileşen belleğe nasıl yüklenir?

Header dosyaları

GCC Dökümantasyonunda Libc – Using the Library – Header Files kısmından alıntı yaparak başlayalım:

C Programları tarafından kullanılan kütüphaneler iki kısımdan oluşur. Bunlar: tipleri, makroları, değişken ve fonksiyon bildirimlerini(declaration) içeren header dosyaları ile fonksiyonların ve değişkenlerin tanımlamalarını içeren(definition) gerçek kütüphane veya arşivlerdir.

GNU C Kütüphanesindeki bileşenleri kullanabilmek için, programların kaynak kodlarının, ilgili bileşene referans veren header dosyalarını içerdiğinden emin olmamız gerekir. Compiler, derleme aşamasında bu referansları işler. Program derlendikten sonra, Linker, bu referansları arşiv dosyalarının birinde bulunan gerçek tanımlamalar(.o dosyası) ile değiştirir(çözümler – resolve).

NOT: Yukarıdaki cümleden şunları çıkarabiliriz:

  • Header dosyalarının yerlerini bulan C Preprocessor programıdır. (Dolaylı olarak yaptığım bir çıkarım.)
  • Header dosyalarının belirttiği object file‘ların yerini bulup derlenecek olan kaynak kodun üreteceği object file ile birleştiren ise Linker programıdır.

GNU CPP Manual‘de Header Files kısmında yazılı açıklamalardan gidersek;

Header dosyalarının iki amacı vardır:

  • User Header Files, kullanıcının bir programını oluşturan kaynak kodlar arasındaki arayüzlerdir. #include "file" yapısı kullanılır.
  • System Header Files, işletim sistemi parçalarına arayüz belirtir. Bunları ekleyerek, sistem çağrısı yapabilmek ya da bu çağrılarla ilgili C Kütüphane bileşenlerini çağırabilmek için gerekli bildirim ve tanımlamaları kodumuzda belirtmiş oluruz. #include <file> yapısı kullanılır. file ismiyle belirtilen dosyayı, sistem dizinleri listesinde arar. Bu liste bir standarttır. Bu listeye yeni bir dizin eklemek için -I bayrağı kullanılabilir.

NOT: Burada bir listeden ve -I bayrağından(parametresinden) bahsediliyor. Böylelikle yazının başında sorduğum ilk iki soruya cevap almaya başladık.

Bu sistem dizinleri listesindeki dizinler ve -I bayrağı ile belirtilerek eklenen dizinler, search path kavramını ortaya çıkarır. Buna ileride değineceğim.

Devam edelim,

-I parametrenin nasıl kullanılacağını anlatan Invocation kısmına referans veriliyor. Burada,

Genelde, C Preprocessor‘unu özellikle çağırmazsınız; C Compiler‘ını çağırdığınızda, derleme aşamasına geçmeden önce zaten otomatik olarak çağrılır. Ancak, C Preprocessor‘unu bazen kendi başına çağırmak da gerekebilir.

diye söze giriliyor. Buradan, daha önce de belirttiğim gibi, header dosyalarının yerlerini C Preprocessor‘un bulduğunu anlarız. C Preprocessor‘unu çağırmak için cpp kullanılır.

Preprocessor‘u gcc ya da cpp ile çağırırsanız, öncelikle derleyici sürücüsü(compiler driver) çalıştırılır. Bu programın amacı, parametreler ile birlikte girilecek olan komutu, bu komutta belirtilen gerçek işi yapacak olan program çağrılarına dönüştürmektir.

cpp için belirtilen komut parametrelerinin hepsi gcc için de kullanılabilir ve aynı anlamdadır. Ancak, C Compiler‘ı, çıktı dosyasını(output file) belirtmek için farklı kurallar kullanır.
C Preprocessor‘ı iki argümana sahiptir. Bu argümanlar, sırasıyla infile ve outfile‘ı belirtir. infile‘da belirtilen dosyalar include dosyalarıdır. Bu dosyalar birleştirilerek belirtilen outfile‘a yazılır.

Parametre Yapısı

-I parametresinden sonra yazılacak olan argümanlar -Ifoo şeklinde birleştirilerek ya da -I foo şeklinde ayrı belirtilerek yazılabilir.
Birçok parametre birden fazla harften oluşabilir (-undef gibi). Bu nedenle ayrı parametreler(mesela -d -M) gruplanarak yazılamaz (-dM gibi).

Search Path

GNU GCC Manual Search Path‘te yazılı bilgilerden devam edelim:

GCC -dolaylı olarak cpp denmek isteniyor- , header dosyaları için çeşitli farklı yerleri arar.

Buradan sonra ilk iki sorunun cevabına daha çok yaklaşıyoruz;

Normal bir UNIX Sisteminde, aksi belirtilmedikçe, #include <file>‘da belirtilen bir file, aşağıdaki dizinlerde aranır:

/usr/local/include
*libdir*/gcc/*target*/version/include
/usr/*target*/include
/usr/include

Yani, bu dizinler header dosylarının varsayılan dizinleri, sistem dizinleridir.
GCC, kurulu olduğu sisteme göre derlenmek üzere ayarlanmıştır. Yukarıdaki, target, bu sistemi belirtir. Kullandığım sistemde, bu, i486-linux-gnu.
Yukarıdaki, libdir, Standard C Library’nin kurulu olduğu dizindir. Kullandığım sistemde(ubuntu), lifeinbeta@lifeinbeta:~$ locate /gcc diye aratarak /usr/lib/gcc‘de olduğunu buldum.

Search path ile ilgili parametreler öncelikle -I, -nostdinc, -include, -isystem ve -iquote dir. Bunlara sırayla bakalım:

  • -I *dir* ve -iquote *dir*: Bundan zaten bahsetmiştim. Kullanıcının kendi kütüphanelerini oluşturmak için yazdığı header dosyaları için belirtilir. Sistem dizinleri listesinden önce belirtilen dizinlerde arama yapılır. Bu, hem #include <file> hem de #include "file" ile belirtilen dosyalar için bu anlama gelirken; -iquote ile , adından da anlışılacağı gibi, kodda sadece #include "file" ile belirtilen dosyalar için belirtilen dizinde arama yapılır. #include <file> dosyaları için bu parametreyle search path‘e eklenen dizinde arama yapmaz.
  • -isystem *dir*: -I ile belirtilen dizinleri standart sistem dizinlerinden biriymiş gibi göstermek amacıyla kullanılır. GCC Manual System Headers kısmında açıklanan amaçlardan ve nedenlerden dolayı böyle bir şeye ihtiyaç duyulabilir. İşletim sistemi ve libc arasında arayüz belirten header dosyarı normal header dosyalarından farklıdır. GCC, standart sistem header‘larına farklı davranır.
  • -nostdinc: cpp‘ye Header dosylarının bulunduğu sistem dizinlerinde(varsayılan dizinler) arama yapmamasını söyler. Sadece -I ya da -iquote parametreleri ile belirtilen dizinlerde arama yapılır. Libc olmadan program yazmak yazısında -nostdlib den bahsetmiştim. Bu parametre Linker yani ld‘nin kullandığı bir parametreydi. Bu parametre ise daha cpp aşamasında iken libc‘nin include dosyalarının dikkate alınmamasını söylemek için kullanılır. Bu yüzden bu seçenek, işletim sistemi kernel‘ını derlerken kullanılabilir ya da standart C kütüphanesini kullanmayan programları yazarken kullanılabilir.
  • -include *file*: Kodda, #include "file" şeklinde yazılmış gibi .c dosyasının başına ekler. #include "file"‘da belirtilen file aranmaya başlanırken önce .c‘nin bulunduğu dizin aranır eğer burada bulunamazsa varsayılan dizinlere bakılır. Bu parametre ile önce varsayılan dizinlere bakılır sonra .c‘nin bulunduğu dizine bakılır.

Uygulama ve Açıklamalar

lifeinbeta@lifeinbeta:~$ cd /usr/lib/gcc
lifeinbeta@lifeinbeta:/usr/lib/gcc$ ls
aotcompile.py  aotcompile.pyc  classfile.py  classfile.pyc  i486-linux-gnu
lifeinbeta@lifeinbeta:/usr/lib/gcc$ cd i486-linux-gnu
lifeinbeta@lifeinbeta:/usr/lib/gcc/i486-linux-gnu$ ls
4.4  4.4.3
lifeinbeta@lifeinbeta:/usr/lib/gcc/i486-linux-gnu$ cd 4.4.3
lifeinbeta@lifeinbeta:/usr/lib/gcc/i486-linux-gnu/4.4.3$ ls
cc1          crtend.o       include        libgomp.a           libsupc++.a
cc1plus      crtendS.o      include-fixed  libgomp.so          SYSCALLS.c.X
collect2     crtfastmath.o  libgcc.a       libgomp.spec
crtbegin.o   crtprec32.o    libgcc_eh.a    libssp_nonshared.a
crtbeginS.o  crtprec64.o    libgcc_s.so    libstdc++.a
crtbeginT.o  crtprec80.o    libgcov.a      libstdc++.so
lifeinbeta@lifeinbeta:/usr/lib/gcc/i486-linux-gnu/4.4.3$ cd include
lifeinbeta@lifeinbeta:/usr/lib/gcc/i486-linux-gnu/4.4.3/include$ ls
ammintrin.h     float.h            mm_malloc.h  stdbool.h    wmmintrin.h
avxintrin.h     immintrin.h        nmmintrin.h  stddef.h     x86intrin.h
bmmintrin.h     iso646.h           omp.h        stdfix.h     xmmintrin.h
cpuid.h         mm3dnow.h          pmmintrin.h  tmmintrin.h
cross-stdarg.h  mmintrin-common.h  smmintrin.h  unwind.h
emmintrin.h     mmintrin.h         stdarg.h     varargs.h

Yukarıda, header dosyalarının bulunduğu standart sistem dizinlerinden biri olan *libdir*/gcc/*target*/version/include dizinin içeriği görünüyor. Buradaki header dosyaları, GCC – derleyici uygulamasının, kendi işlevleri için kullandığı kütüphanelere arayüz sağlayan dosyalardır. Burada ayrıca libc‘de standart olarak belirtilmeyen ama GNU‘nun libc‘yi gerçeklerken ek olarak koyduğu fonksiyonların gerçeklemelerine arayüz sağlayan header dosyaları da vardır. GCC, libc‘nin bütün header’larını kullanabilir. Detaylı bilgi için buraya bakılabilir.

lifeinbeta@lifeinbeta:/usr/local/include$ ls
lifeinbeta@lifeinbeta:/usr/local/include$ cd /usr
lifeinbeta@lifeinbeta:/usr$ ls
bin  games  include  lib  lib64  local  sbin  share  src
lifeinbeta@lifeinbeta:/usr$ cd include
lifeinbeta@lifeinbeta:/usr/include$ ls
aio.h           expat_external.h  libexslt         nl_types.h   sysexits.h
aliases.h       expat.h           libgen.h         nss.h        syslog.h
...
err.h           jpegint.h         netinet          stdio.h      xen
errno.h         jpeglib.h         netipx           stdlib.h     xlocale.h
error.h         langinfo.h        netiucv          string.h     xorg
...
...
...

Yukarıda, bahsettiğim standart sistem dizinlerinden sadece /usr/include da header dosyaları bulundu. Bu dosyaların bir özeti gösteriliyor.

Bu yazı yine uzadı gitti 🙂 Neyse ki markdown‘la kolayca yazabildim. 😛 Böylelikle, yıllarca(!) aklımın bir köşesinde takılı kalan bazı noktaları temizledim. Ayrıca, bu yazı sadece hobi olarak yazılmış bir yazı değil. Bir Nerd işi de değil. Sadece, herhangi büyük bir C uygulaması ile uğraşırken ya da harici bir C kütüphanesini kullanırken kafam rahat olsun diye 🙂 yazdığım bir yazı. Neyse, son iki soru kaldı. Onları bir sonraki yazımda, Linker‘dan bahsederken cevaplayacağım.

GCC Ortam Değişkenleri

CPATH
C_INCLUDE_PATH
CPLUS_INCLUDE_PATH
OBJC_INCLUDE_PATH

Yukarıdaki Ortam Değişkenlerinin değeri, terminalden;

lifeinbeta@lifeinbeta:~$ echo $C_INCLUDE_PATH

lifeinbeta@lifeinbeta:~$ echo $CPATH

lifeinbeta@lifeinbeta:~$ echo $OBJC_INCLUDE_PATH

şeklinde yazılarak görülebilir.


LİNKLER

Advertisements

1 Comment

Filed under gcc, linking

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!]

3 Comments

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