Monthly Archives: May 2011

UNIX – i-node, Hard Link ve Soft Link


Making symlinks to replace GLUT yazısını okurken;

ln -s /usr/lib/libfreeglut.so.0.2.0 /usr/lib/libglut.so.3
ln -s /usr/lib/libfreeglut.so.0.2.0 /usr/lib/libglut.so.3.7
ln -s /usr/lib/libfreeglut.so.0.2.0 /usr/lib/libglut.so.3.7.0

ile karşılaştım ve neden link yapılmaya gerek duyulduğunu merak ettim.

Değişik versionlardaki .so ları(.so,.so.3,.so.3.9) tek bir versiyona(.so.0.2.0) link ettik. Ayrıca, kütüphane ismini(libglut) de daha belirgin bir hale(libfreeglut) getirdik. Artık, libglut kütüphanelerinden herhangi biri yerine sadece libfreeglut.0.2.0‘ı kullanabiliriz. Sanırım, bu şekilde olası karmaşaları önledik.

i-node

UNIX’te her dosya, dosya içeriğiyle ilgili önemli bilgiler tutan, i-node denilen, bir veri yapısıyla ilişkilendirilir. i-node; dosyanın boyutu, son erişim zamanı, son değiştirme zamanı, kendisinin en son değiştirilme zamanı, dosyanın sahibi, izin bilgileri, dosyanın disk üzerinde bulunan verisine olan pointer‘ları ve dosyaya verilen link sayısını tutar. UNIX’te dizinlere karşılık gelen veri yapısı, dizindeki dosya isimlerini tutar. Bir dosya ismi, ilişkili olduğu i-node‘a pointer dır.

Dosya ismi, veri yapısına bir pointer ise bu veri yapısına erişmek için başka pointer‘lar da kullanabiliriz. Bunun çeşitli amaçları vardır. Dosya ismi harici böyle pointerlara link denir. Hard Link ve Soft Link vardır.

Hard Link, bir dosyanın i-node‘una pointer‘ olan başka bir dosyadır. Elimizde file1 olsun. İçeriği bravo, milord! 🙂 olsun. Bu dosyaya file2 isimli bir dosyayı link edelim.
ln file1 file2
Sağdan sola okunursa, file2,file1‘e link edilmiştir. Şimdi cat file2 dersek, file2‘nin de bravo milord! dediğini görürüz. Yani, bu iki dosya ismi de aynı i-node‘a pointer oldu.

Soft/Symbolic Link, oluşturulduğunda, file2 olsun ve file1‘e link edilmiş olsun, file2‘nin içerisinde file1 yazılır. Bu şekilde file1‘in içeriğine file2 ile erişebiliriz. ln -s file1 file2 şeklinde yazılır.

lifeinbeta@lifeinbeta:~/Desktop$ touch file1
lifeinbeta@lifeinbeta:~/Desktop$ vim file1
lifeinbeta@lifeinbeta:~/Desktop$ cat file1
bravo milord!
lifeinbeta@lifeinbeta:~/Desktop$ ln -s file1 file2
lifeinbeta@lifeinbeta:~/Desktop$ ls -F
...
file1                                 ...
file2@                                ...
lifeinbeta@lifeinbeta:~/Desktop$ cat file2
bravo milord!

Görüldüğü gibi, sembolik link @ işareti ile belirtiliyor. Hard Link‘te olduğu gibi, link edilmiş olan dosyanın içeriğini değiştirdiğimizde gerçek dosyanın içeriği de değiştirilmiş olur.

UNIX’te bir dosya sistemi hiyerarşisi, birden fazla disk ya da disk bölümü üzerindeki dosya sistemlerinden oluşabilir. Her dosya sistemi, kendisinden sorumludur, diğer dosya sistemleri hakkında bilgiye sahip değildir.

Hard Link‘ler, bir dosya sisteminin kendi veri yapısı olan bir i-node ile ilişkili olduğu için, sadece kendi dosya sistemi hakkında bilgiye sahiptir. Bu nedenle, bir dosya sistemindeki bir dosyadan, başka bir dosya sistemindeki bir dosya için oluşturulamaz.

Soft linkler ise dosya ismini içerdiği için bu durumdan etkilenmez. freeglut için aynı dosya sistemi içinde çalışacağımdan aslında Hard Link de oluşturabilirdim.

1 Comment

Filed under UNIX

C – Linker ve Nesne Dosyaları


Header Dosyalarının yerleri ve Search Path Ayarları yazısının girişinde sorduğum sorulardan;

  • Bir Header dosyasının belirttiği bir object file nasıl bulunur? Linker bunu nerede arar?

sorularını burada cevaplayacağım. Bunu yaparken, Linker ve Linker parametrelerinden söz edeceğim.

Object File

Stackoverflow‘da Why compile to an object file first? sorusuna verilen cevaplarda önce object code‘a derleme konusunda aşağıdaki noktalara değiniliyor.

Bu olaya, separate compilation denir.

  • Aynı anda, farklı object file‘larda birden fazla kişi çalışabilir.
  • Daha hızlı derleme, aynı dosyaları tekrar tekrar derlemene gerek yoktur.
  • object file‘lar farklı dillerden derlenerek oluşturulmuş olabilir. Daha sonra, Linker ile birleştirilebilirler.
  • separate compilation birçok sistem geneli library‘nin dağıtımını kolaylaştırır. Bunlar static ya da shared library‘ler olabilir. Mesela; işletim sistemi kütüphaneleri, dillerin standart kütüphaneleri, ya da üçüncü parti kütüphaneler.

Yukarıda anlatılanlardan, Make vs. Ant – Build araçları ile konfigürasyon yönetimi yazısında bahsetmiştim. Burada, object file‘ları birleştirmek Linker‘ın işidir.

gcc ile Link işlemini, çeşitli parametreler kullanarak yapabiliriz. Ayrıca, binutils den biri olan ld ile de bu işlemi linker adımı içerisinde yapabiliriz.

GCC ve Linker Parametreleri

Options for Linking‘de link aşamasında kullanılabilecek olan parametreler açıklanıyor.

gcc -c file1.c file2.c

Yukarıdaki yapıdaki -c, compile demektir, bu parametre Derleyicinin kullandığı bir parametredir.  Belirtilen kaynak kodların object filelarını oluşturur. linker bu parametreyi, -S ve -E parametrelerinden biriyle işlem yapıldığında görmezden gelir.

gcc -o exe_dosya file1.o file2.o

Yukarıdaki yapıda, -o (output) parametresi belirtildiği için, belirtilen object file‘lar, Linker tarafından Link edilerek executable oluşturulur. Dolayısıyla, Linker‘ın en temel parametresi, .o uzantılı object file‘dır. Ama çoğu zaman tek başına .o lar ile işlem yapılmaz. Kütüphaneler(shared->.so ya da static->.a) ile işlem yapılır.

C ile LIBC’yi Kullanmadan Program Yazmak yazısında bahsettiğim -nostdlib‘e de değinmek isterim.

gcc -nostdlib -o hello hello.c

-nostdlib Linker‘a, .c dosyası derlendikten sonra oluşan .o dosyasını libc kütüphanesiyle link etmemesini söylüyor. Yoksa bu .o, .c dosyasında #include <stdio.h>, #include <stdlib.h> ... ifadeleri belirtilmemiş bile olsa standart kütüphaneyle link edilirdi. Bu parametre ile .o dosyasına link edilmeyecek olan libc kütüphanesi,

/usr/lib/gcc/i486-linux-gnu/4.4/libgcc.a

dir.

Bununla birlikte hazır bakmışken libgcc ile ilgili başka nerelerde neler var onlara da bakalım.

lifeinbeta@lifeinbeta:/$ sudo find / -name libgcc*
/opt/lampp/lib/libgcc_s.so.1
/usr/share/lintian/overrides/libgcc1
/usr/share/doc/libgcc1
/usr/lib/ure/lib/libgcc3_uno.so
/usr/lib/gcc/i486-linux-gnu/4.4/libgcc_s.so
/usr/lib/gcc/i486-linux-gnu/4.4/libgcc.a
/usr/lib/gcc/i486-linux-gnu/4.4/libgcc_eh.a
/usr/lib/libgccpp.so.1
/usr/lib/libgccpp.so.1.0.2
/var/lib/dpkg/info/libgcc1.md5sums
/var/lib/dpkg/info/libgcc1.shlibs
/var/lib/dpkg/info/libgcc1.postrm
/var/lib/dpkg/info/libgcc1.symbols
/var/lib/dpkg/info/libgcc1.postinst
/var/lib/dpkg/info/libgcc1.list
/lib/libgcc_s.so.1

-llib: Linker, belirtilen object file‘ı, burada belirtilen library ile link eder.

UNIX’te bir kütüphanenin ismi, “liblibrary_name(.ar | .so)” şeklindedir. Link edilmesi istenen kütüphane, -llibname şeklinde değil, -lname şeklinde belirtilmelidir.

gcc parametreleri belirtilirken, -l parametresinin hangi sırada belirtildiği önemlidir. Eğer, foo.o -lname zoo.o şeklinde bir ifade varsa, zoo.o, name kütühanesindeki gerekli .o ile link edilmez. Çünkü, Linker, object file‘ları ve kütüphaneleri, gcc komutunda bunların belirtildiği noktaya gelince arar ve sadece o noktada işlem yapar. Yukarıda, -lname, bütün object file‘lardan sonra belirtilmiş olsaydı, Linker, önce object file‘ları işleyecekti sonra en sonda belirtilen kütüphaneyi arayacaktı ve bulduğunda bunu, önceden farkettiği object file‘lar ile link edebilecekti.

Peki, yukarıda sadece library ismini belirttik. UNIX üzerinde yüzlerce dizin olduğunu hesaba katarsak, Linker belirtilen kütüphaneyi hangi dizinde bulacak? Linker, gerekli olan bir kütüphaneyi, bir standart dizin listesindeki dizinlerde arar. Daha sonra bu kütüphanenin içinden gerekli olan object file‘ın nasıl alınarak nasıl link edildiğini Loaders yazısında anlatacağım.

Library Search Path = Link Path = Load Path

setting search paths‘te ve When should i set LD_LIBRARY_PATH‘te belirtildiği gibi;

Linker, kütüphaneleri sırasıyla /usr/lib/ ve /usr/local/lib/ dizinlerinde arar. Bu dizinler, link edilecek dosyaların aranması için kullanılan, varsayılan dizinlerdir. Bu dizinlere, library search path ya da link path ya da load path denir. Standart C Kütüphanesi olan libgcc, /usr/lib/ altında bulunur ve varsayılan olarak her zaman link edilir. Link işleminde kullanılması gereken harici bir kütüphanenin bu dizin listesine eklenmesi gerekir.

Load Path‘e yeni bir dizin, LD_LIBRARY_PATH ortam değişkeni aracılığıyla eklenebilir(Ancak bu, güvenlik nedeniyle, tavsiye edilmez). Bu değişken, varsayılan dizinleri tutmaz, sadece harici olarak eklenmiş dizinleri tutar. Ortam değişkenine eklemekle, yeni eklenen dizin başka link işlemlerinde de aranması için kalıcı hale getirilmiş olur. Sadece uygulamaya göre, Linker tarafından içinde kütüphane aranacak olan, dizin belirtmek için(tavsiye edilen) -Ldizin parametresi kullanılmalıdır.

Ancak, -L parametresiyle link path‘e her link işlemi için dizin belirtmek istemeyiz. Aynı şekilde, -l parametresiyle link edilmesini istediğimiz library‘leri belirtirken de tekrar tekrar bu işi yapmak istemeyiz. Bunlara alternatif olarak, makefile kullanabiliriz. Aşağıda örnek bir Makefile var.

CC = gcc -Wall
SOURCE = hello.c
FLAGS = -lglut
BINARY = hellogl

all:
        $(CC) $(SOURCE) -o $(BINARY) $(FLAGS)

clean:
        @echo Temizleniyor...
        @rm $(BINARY)
        @echo Temiz!

freeglut3 Kurulumu

Şimdi, freeglut3‘ü sistemimize yükleyelim.

Bunun için, OpenGL Utility Toolkit(freeglut3) ü kurduktan sonra aşağıdaki işlemi yaptım.

lifeinbeta@lifeinbeta:/$ sudo find / -name freeglut*
/usr/share/doc/freeglut3
/var/cache/apt/archives/freeglut3_2.6.0-0ubuntu2_i386.deb
/var/lib/dpkg/info/freeglut3.postinst
/var/lib/dpkg/info/freeglut3.list
/var/lib/dpkg/info/freeglut3.postrm
/var/lib/dpkg/info/freeglut3.shlibs
/var/lib/dpkg/info/freeglut3.md5sums

Henüz .h ve .so dosyları yok. Daha sonra, development libraries and headers for GLUT(freeglut-dev)i kurdum ve aşağıdaki işlemi yaptım.

lifeinbeta@lifeinbeta:/$ sudo find / -name freeglut*
/usr/share/doc/freeglut3
/usr/share/doc/freeglut3-dev
/usr/share/doc/freeglut3-dev/freeglut.html
/usr/share/doc/freeglut3-dev/freeglut_user_interface.html
/usr/share/doc/freeglut3-dev/freeglut_logo.png
/usr/include/GL/freeglut_std.h
/usr/include/GL/freeglut.h
/usr/include/GL/freeglut_ext.h
/var/cache/apt/archives/freeglut3_2.6.0-0ubuntu2_i386.deb
/var/cache/apt/archives/freeglut3-dev_2.6.0-0ubuntu2_i386.deb
/var/lib/dpkg/info/freeglut3-dev.list
/var/lib/dpkg/info/freeglut3.postinst
/var/lib/dpkg/info/freeglut3.list
/var/lib/dpkg/info/freeglut3.postrm
/var/lib/dpkg/info/freeglut3.shlibs
/var/lib/dpkg/info/freeglut3-dev.md5sums
/var/lib/dpkg/info/freeglut3.md5sums

Header dosyaları, /usr/include/ içinde /GL/ dizini oluşturularak buraya konuldu. Neden buraya konduğu konusunda bkz. Header dosyalarının yerleri ve Search Path ayarları. Ancak hala kütüphaneler ortada yok. development libraries … dediğine göre yüklenmiş olmalılar, ama bulamadım. 🙂 How to install freeglut?‘dan bakarak bu kütüphanelerin libglut isminde olduğunu gördüm.

Aşağıdaki işlemi yaptığımda bu dosyaları görebildim.

lifeinbeta@lifeinbeta:/$ sudo find / -name libglut*
/usr/share/doc/libglut3-dev
/usr/lib/libglut.so.3
/usr/lib/libglut.so
/usr/lib/libglut.so.3.9.0
/usr/lib/libglut.a
/var/cache/apt/archives/libglut3-dev_3.7-25_all.deb
/var/lib/dpkg/info/libglut3-dev.list
/var/lib/dpkg/info/libglut3-dev.md5sums

Linkler

Leave a comment

Filed under build, C, gcc, linker

C – Libc ve Runtime


ÖNEMLİ NOT: Bu yazı, alıntılara rağmen, kişisel yorum içermektedir. Bu yazıyı bunu dikkate alarak okuyun. Daha önceden bu konuyla ilgili okuduklarınızı ya da bildiklerinizi değerlendirmek için okumanız daha faydalı olacaktır. Böyle önemli bir konuda kimseyi yanıltmak istemem. Yazıyı okuduktan sonra eksik olan ya da yanlış yaptığım kısımlar için yapılacak yorumlara minnettar olacağım.

Stackoverflow‘da, What is the C Runtime sorunda;

Microsoft runtime library, Windows İşletim Sistemi için programlama yapabilmek için, fonksiyonlar sağlar. Bu fonksiyonlar, C ve C++ dilleri tarafından sağlanmayan birçok programlama işlevini otomatize eder(çağrılabilir halde sağlar).

Peki, mesela libcmt.lib nedir? Bu sağlanan fonksiyonlar hangileridir? C standard library‘sini, C Derleyicisinin bir bir parçası olarak biliyorum; o halde libcmt.lib, C standard library’sindeki fonksiyonların Win32 ortamında çalışabilir halde olan bir gerçeklemesi mi?

Öncelikle bu soruda belirtilen ifadelere bakalım. İlk paragrafta, Microsoft Runtime Library ile sağlandığı söylenen fonksiyonlar, işletim sistemi çağrılarıdır.

C Wrapper Fonksiyonları ve Sistem Çağrıları

Libc‘deki birtakım C fonksiyonları(IO fonksiyonları -> fopen(), open(), fdopen(), open64(), ...; Dizin fonksiyonları -> chdir(), rename(), rmdir(), mkdir(), ...; Diğer fonksiyonlar -> ... ) işletim sistemi çağrılarının Wrapper‘larıdır(BSD Bu fonksiyonların kullandıkları sistem çağrıları BSD UNIX için sırasıyla, IO sistem çağrıları için -> open; Dizin sistem çağrıları için; chdir, rename, rmdir, mkdir şeklindedir.).

  1. Wiki – Wrapper Library
  2. Wİki – System Call
  3. BSD UNIX System Calls List #Çok çok önemli 🙂
  4. Dosya Erişimi ve Dizin İşlemleri için Sistem Çağrıları
  5. UNIX Sistem Çağrılarını ve Libc Kütüphane Fonksiyonlarını Kullanmak

Soruya verilen cevaplardan;

libcmt

libcmt, C standart kütüphanesindeki fonksiyonların, Microsoft‘un C derleyicisi tarafından derlenip oluşturulmuş gerçeklemelerinden birisidir.

Microsoft‘un birçok libc gerçeklemesi(implementation) vardır. Bunlar;

  1. Her zaman statically linked olan single threaded gerçekleme, (libc.lib)
  2. Multi-threaded; (NOT:libcmt‘deki mt buradan gelir.)
    1. statically linked,(libcmt.lib)
    2. dynamically linked,(libcmt.dll)
  3. Bütün bu gerçeklemelerin debug ve release versiyonlarıdır.(libcmtd, libcmt)
C Standard Library API
libc(C Standart Kütüphanesi); bu kütüphanenin ister Win32 gerçeklemesinin olsun, ister UNIX gerçeklemesinin olsun ya da başka bir işletim sisteminin ve/veya donanımın(işlemcinin komut kümesi); bu kütüphaneyi gerçeklerken mutlaka gerçeklemeleri gereken standart fonksiyon arayüzleri(interface) kümesi tanımlar. Bu gerçeklemeleri yapanlar bu standart kütüphaneyi gerçeklemenin yanında ekstra fonksiyonlar da sağlar. Ayrıca bu vendor‘lar, sadece kendi Compiler programları tarafından kullanılması için birkaç fonksiyon daha tanımlayabilirler.

libcmt içerisinde gerçeklenmiş fonksiyonların listesini görebilmek için; Visual Studio Komut Satırında;

libcmt.dll nin bulunduğu dizine geçildikten sonra,
lib -list libcmt.lib
yazıldığında, bu kütüphanedeki fonksiyon gerçeklemelerinin bulunduğu dosyaların (object file -> .o) uzun listesini üretir. Bu listedeki isimler, C Standart Kütüphane API’sindeki fonksiyon isimlerine tamı tamına uymamakla birlikte, benzerler. Bir dosyada birden fazla fonksiyon gerçeklemesi olabilir. Bu yüzden, belli bir .o dosyasının içeriğine bakmak için, önce bu dosya açılır:
lib -extract
dosya açıldıktan sonra içindeki herhangi bir fonksiyon gerçeklemesine,
dumpbin /symbols
ile bakılabilir.

UNIX ortamları için kullanılan derleyicilerden birisi GNU C Compiler(gcc) dır. C standart kütüphanesinin bu derleyici ile gerçeklenmiş hali glibc dir.

Linux From Scratch‘ta(Adım-adım işlemlerle tamamen kaynak koddan kendine ait değiştirilmiş bir Linux sistemini oluşturmak.) glibc kısmında, glibc‘nin kurulumundan, ayarlarından, Dynamic Loader‘ın ayarlanmasından ve glibc‘nin içindeki dosyalardan bahsediliyor.

 

Derleyiciler 

  1. GNU C Compiler – GCC – Dökümantasyonu‘nda GCC ve bileşenleri hakkında bilgiler mevcut. GCC dökümantasyonunda Standard Libraries kısmıda, GNU C kütüphanesinin neredeyse tam olarak C99 standardını desteklediği belirtiliyor. Extensions to the C Language Family kısmında, GNU C, ISO Standard C‘de bulunmayan bir takım dil özellikleri sağlar deniliyor.
  2. lcc-Win32 A Compiler System for Windows: Windows için yapılmış, GCC’ye göre daha basit bir derleyici sistemi. Bu sistem bir Code Generator(Compiler, Assembler, Linker, Resource Compiler ve Librarian-Library Manager), IDE(editor, debugger, makefile generation, resource editor etc.) içerir.
  3. Pelles C: Windows ve Windows Mobile için bir complete development kit. Burada özellikler kısmında, C99 standardını desteklediği belirtiliyor. Yani, bu derleyici C99 specification‘unda belirtilen fonksiyonların gerçeklemelerini içeriyor.

Runtime ve Runtime Library

C ve C++ Runtime‘ları, fonksiyonlar kümesidir. .NET Runtime ise IL interpreter, garbage collector ve dahasıdır.
diye söyleniyor. Bu cevap çok açık değil. Hangi fonksiyonlar(C Runtime Library API‘sinde belirtilenler mi ve bunlardan başka hangileri) açıkça belirtilmiyor. Ayrıca, .NET library‘si denmiyor ve bu ifadeden .NET Runtime‘ının NET Runtime Library’sini içine aldığı ima ediyor. Buradan, Runtime‘ın Runtime Library‘i içerdiği sonucuna varabiliriz.

Diğer bir açıklamada Wikipediaruntime library/run-time system‘a atıf yapılarak,

Runtime library, derleyici tarafından kullanılan özel bir program kütüphanesidir. Bir programın çalışması anında, programlama diline yerleşik olan fonksiyonları(built-in) gerçeklemek için kullanılır. Bu fonksiyonlar; IO fonksiyonları ya da  Memory Management fonksiyonları gibi fonksiyonlardır.
diye söyleniyor. Bu cevap çok daha açık bir cevap. Özellikle, derleyici tarafından kullanılan özel bir program kütüphanesi açıklaması. C’de bir kaynak kod, object file‘a dönüştüğünde  gerçeklenmiştir. Bu işlem için libc kulanıldığına göre Runtime Library, Libc‘yi içerir.

NOT: Bununla birlikte, IO işlemleri ve Memory Management fonksiyonları için işletim sistemine yapılan sistem çağrıları kullanılır. Yukarıda, Microsoft Runtime Library‘nin sağladığı, C ve C++’da olmayan, fonksiyonların sistem çağrıları olduğunu söylemiştim.

Runtime-system(kısaca runtime da denir), belli bir programlama diliyle yazılmış programların çalışmasını desteklemek için tasarlanmış yazılımdır.

Runtime System, temel alt-seviye komutların gerçeklemelerini ayrıca yüksek seviye komutların gerçeklemelerini yapar ve type checkingdebugging, kod üretme ve optimizasyon sağlar. Runtime System‘in bazı servisleri programlama dilinin API‘si ile programcıyı sağlanır ama diğer servisler(task scheduling ve kaynak yönetimi gibi.)

Bu açıklama diğerlerine göre daha net bir açıklama. Çünkü;
Runtime‘ın o dilin derleyici yazılımını içine aldığı, çalıştırılabilir dosyayı çalıştırabilmek için işletim sisteminin birtakım araçlarının bir şekilde kullanıldığı belirtiliyor.

Bu noktadan sonra şu sonuçlara varabiliriz:

  1. Runtime = Runtime Library = Runtime System
  2. Runtime, İşletim Sistemi Çağrılarının bulunduğu işletim sistemi API‘si + Derleyicinin Kendi işlevleri için kullandığı kütüphane + Derleyici Programın kendi amacını yerine getirmek için kullandığı fonksiyonlar + Libc(Standart C Kütüphanesi) fonksiyonlarıdır.
  3. Yukarıdaki cümleden ve yazının genelindekilerden şunu çıkarabiliriz: Derleyici; kendi kütüphanesini, Libc‘yi, işletim sistemi API‘sini kullanarak çalıştırılabilir dosya oluşturur. Bu çalıştırılabilir dosya, işletim sisteminin Loader‘ı tarafından belleğe yüklenir ve işletim sistemi tarafından Process oluşturulur.

Çalıştırılabilir Dosya ve İşletim Sistemi

Stackoverflow‘da Windows ve Linux tarafından üretilmiş olan çalıştırılabilir dosyaların farklı yanları nelerdir? diye soruluyor. Soruda,

Linux‘ta oluşmuş olan bir çalıştırılabilir dosya neden Windows‘ta çalıştırılamıyor? Windows ve Linux‘ta ortak olan işletim sistemi çağrılarını kullanan bir C programı yaparsak, derleyici, Linux ve Windows için farklı binary‘ler mi üretir?

diye soruluyor. Sorunun cevabına geçmeden önce şunu söylemek isterim: Soruda zaten bir çalıştırılabilir dosyanın işletim sistemi tarafından üretildiği söyleniyor. Eğer bir çalıştırılabilir dosya farklı işletim sistemi tarafından üretilmişse, düz bir mantıkla düşünürsek, oluşan çalıştırılabilir dosyalar da farklı olacaktır. Bu ima, tam olarak doğru değil ki zaten sorunun açıklamasında, bunun derleyici tarafından oluşturulduğu belirtiliyor. Ancak, işletim sistemi tarafından  oluşturulmuş diye biraz genel bir tabir kullanılmasının sebebi, işletim sistemi çağrıları kullanılarak oluşturulmuş denmek istenmesidir. Farklı işletim sistemi denerek de, her işletim sistemine özgü farklı işletim sistemi çağrıları denmek isteniyor.

Sorunun cevaplarında,

En büyük problem, işletim sistemi API’leridir. Bir Windows çalıştırılabilir dosyasının process’i oluşturulurken, Windows API‘ı çağrılır. API‘deki fonksiyonlarla işletim sistemine yapılan çağrılar vasıtasıyla; stack ayarlanır, belli bir hafıza bölgesi bu process‘e tahsis edilir. Bir işletim sisteminin API’sindeki çağrılar da o işletim sistemine özgü olduğu için başka işletim sistemlerinde farklıdır. Bu yüzden, bir çalıştırılabilir dosya hem Windows’ta hem de Linux’ta çalışmaz.

Farklı Container formatları kullanırlar; çoğu UNIX çalıştırılabilir dosyası  ELF dosyalarıdır. Windows çalıştırılabilir dosyaları ve DLL‘ler ise PE dosyalarıdır.

diye söyleniyor. Bu işlemler Loader ile ilgilidir. Loader mevzusunu sonraki yazılarımdan birinde ele alacağım.

Güncelleme – Başlangıç: 31.05.2011

İşletim sistemi API’sinden bahsetmişken;

Linux From Scratch – Linux API Headers yazısında Linux API‘sini belirten Header dosyalarının; kısa özetleri, dahil oldukları mantıksal gruplar, bu grupların yerleri anlatılıyor.

Linux API Header‘ları,  glibc‘nin görebilmesini Linux Kernel‘ının API‘sini görebilmesini sağlar. Linux Kernel’ı, sistemin C kütüphanesi(glibc)nin kullanabilmesi için bir API sunar. Bunu, Linux Kernel kaynak kodundaki çeşitli Header dosyaları ile yapar.

Şimdi Linux kaynak kodundan, kullanıcının-görebildiği(user-visibleHeader dosyalarını çıkaralım ve test edelim. Bunlar, kaynak kodda bir yerel alt dizinde bulunur ve kurulumda olması gereken yere kopyalanırlar. Bu işlem esnasında kopyalamanın yapılacağı hedef dizinde önceden var olan dosyalar silinir. Ayrıca, burada kernel geliştiricilerinin kullandığı bazı gizli dosyalar vardır. Kopyalamanın yapılacağı kaynak dizinde, kopyalamadan önce bu dosyalar da silinir.

make headers_check 
make INSTALL_HDR_PATH=dest headers_install 
find dest/include \( -name .install -o -name ..install.cmd \) -delete 
cp -rv dest/include/* /usr/include 

Yukarıdaki son işlemde, hedef dizin

/usr/include

dir. Yani, Linux API Header dosyaları burada bulunur. Ancak, burada belli klasörler içinde mantıksal olarak gruplanmış halde bulunurlar.

Yüklenen Header dosyaları ve bulundukları dizinler:

/usr/include/asm/*.h, 
/usr/include/asm-generic/*.h, 
/usr/include/drm/*.h, 
/usr/include/linux/*.h, 
/usr/include/mtd/*.h, 
/usr/include/rdma/*.h, 
/usr/include/scsi/*.h, 
/usr/include/sound/*.h, 
/usr/include/video/*.h, 
/usr/include/xen/*.h

Yukarıdaki dizin yapısı,

/usr/include/linux/*.h The Linux API Linux Headers

gibidir.

Güncelleme – Bitiş: 31.05.2011

Stackoverflow‘da, Belirli bir işletim sistemine göre değil de belirli işlemci mimarisine göre, bir çalıştırılabilir dosyayı nasıl oluşturabilirim? diye soruluyor. Soruya verilen cevap, Container ve Loader kavramlarına çok iyi bir şekilde değiniyor:

Run” ne demek onun hakkında bir düşünelim… Oluşturulan binary kodları bir şey belleğe yüklemeli. Bu bir İşletim Sistemi özelliği. .exe ya da binary executable file(.exe‘nin UNIX eşdeğeri) ya da bundle ya da her neyse; bunların birçok özelliği işletim sistemine göre biçimlendirilmiştir ki bu sayede işletim sistemi onu belleğe yükleyebilir. I/O rutinleri, OS API‘sini encapsulate eden fonksiyonlardır. Yani OS her yerde.

Burada bahsedilen, çalıştırılabilir dosyaların işletim sistemine göre hazırlanmış formatları ELF ve PE‘dir. “Bu işletim sisteminin bir özelliği” denirken Loader kastediliyor. Bu formatları taslak yapılar olarak, çalıştırılabilir dosyaları da bunların içine koyulan yapılar olarak düşünürsek; bu formatlara Container diyebiliriz.

Eski günlerde, çalıştığımız bilgisayarlarda OS yoktu, C de yoktu. Assembler ve Linker’ları kullanarak büyük binary image‘lar oluşturuyorduk. Bu şekilde bunları makineye yükleyebiliyorduk.

Burada binary image kavramı, çalıştırılabilir dosya kavramının bir başka karşılığı. Container‘a binary image‘ı koyuyoruz diyebiliriz.

Kodu belleğe yükleyebilmek için front panel key‘leri kullanıyorduk ve yükleyeceğimiz kodu okurken punched paper-tape reader a benzeyen kullanışlı bir cihaz kullanıyorduk. Bu, boot linking loader yazılımını yüklerdi. Sonra, bu linking loader bellekte olduğunda tape‘e assembler ile veri yazabilirdik. Kendi device driver’larımızı yazdık ya da kaynak kod biçiminde, punched on paper tapes, kütüphane fonksiyonlarını kullandık. … Sonra, basit API’lere sahip olan basit OS’ler yazdık. Bu basit API‘ler device driver‘lar ve birkaç basit araç olan bir dosya sistemi(file system), bir editör ve bir derleyici yazdık. Bu derleyici Jovial isimli bir dil içindi.

Daha sonra işletim sistemin kullanmadan C++ kodu yazılabileceği belirtiliyor ve bunun için yapılması gerekenler sıralanıyor:
  1. İşlemci chipset‘inin parçaları olan BIOS ve BIOS‘a benzer yapılar hakkında bilgiler öğren. BIOS, ROM üzerine yazılmış olan POST(power-on self-test) işlemini yapan basit sürücüleri yükleyen ve boot block‘u yerleştiren küçük bir işletim sistemidir.
  2. Kendi boot block‘unu yazmayı öğren. Bu, POST işlemi yapıldıktan sonra yüklenecek olan ilk bildiğimiz anlamdaki yazılımdır. Bu şekilde donanım üzerinde kontrolü, işletim sistemine bırakmadan, ele alabilmiş olursunuz.
  3. GRUB, LILO ya da BootCamp‘ın işletim sistemini nasıl başlattığını öğren. Bu programlar yüklendiklerinde artık, kendi programını yükleyebilirsin. Bu durumda donanım üzerinde işletim sistemi değil senin programın çalışıyor olur.
  4. ELF hakkında oku.
  5. Device Driver‘ların nasıl yazıldığını öğren. Eğer işletim sistemi kullanmayacaksan, Device Driver yazmak zorunda kalacaksın.
Bütün bunlarla birlikte, programlama diline yerleşik herhangi bir fonksiyonu kullanmadan, sadece programlama dilinin yazım kuralında belirtilen ifadeleri kullanarak, C ile Standart Kütüphane olan LIBC’yi kullanmadan Program Yazmak yazısında belirttiğim gibi, program yazılabilir. Burada, libc ve işletim sistemi çağrıları kullanılmaz.

Leave a comment

Filed under C

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

1 Comment

Filed under gcc, linking

Markdown ile Markdown Hakkında


Markdown Nedir?

Çoğu zaman, yayınlanacak bir yazı yazılırken üzerinde çok değişiklik yapılması gerekir. HTML, bir publishing(yayın) formatı olduğu için, bir HTML dosyasında hızlı bir şekilde değişiklik ve ekleme yapmak zordur. Bir yazıyı notepad gibi bir editörde açıp orada istediğimiz gibi biçimlendirdikten sonra o yazının son halini HTML’e çevirebilseydik çok hızlı bir şekilde yayın yapmış olurduk. İşte, markdown, plain text(düz yazı) yazı yazarken bu yazıyı formatlı bir şekilde yazmayı sağlar. Böylelikle, bir yazıyı daha sonradan HTML’e dönüştürülebilir biçimde hızlı bir şekilde düzenleyebiliriz.

Bundan sonra uzun blog yazılarını yazmayı kolaylaştırmak için markdown‘ı kullanacağım. Yani, yazıyı baştan sona düz yazı halinde gedit ya da vim ‘de markdown‘ın syntax‘ı ile yazacağım. Bu yazı üzerinde düzenlemeleri bitirdikten sonra bu yazıyı, Markdown.pl ile HTML’e çevireceğim.

Markdown Syntax‘ı neye benzer?

Markdown Syntax‘ı, düz yazı şeklindeki e-posta’larda kullanılan yazı formatından gelir. Yani, belirli noktalama işaretlerinden oluşur.

Markdown Syntax’ta bahsedildiği gibi,

HTML Syntax’ının ufak bir altkümesidir.

HTML Tag’larını eklemeyi kolaylaştırmak için düşünülmüştür.

Mesela, e-posta’larda;

bir kelimeyi vurgulamak için genelde *emphasis* yazılır, yani bir kelimenin etrafına "*" işaretleri koyulur, 
">" işareti, <blockquote></blockquote>'a eşdeğerdir, 
"*" işareti, ve 1. 2. ... gibi listeleme yapısı, sırasıyla, <ul></ul> ve <ol></ol> anlamındadır. 

Syntax Özellikleri

Markdown‘ın kendi basit syntax‘ı ile bir metni düz yazı halinde yazarken, HTML farmatına geçeceğimiz yerlerde bunu ayrıca belirtmeye gerek yoktur. Gerektiği yerde HTML Syntax‘ı ile yazılır ve markdown ile devam edilir. Syntax hakkında hep akılda tutulması gereken birkaç nokta vardır. Bunlar;

  • Block-Level HTML Elemanlarında, herhangi bir eleman(<p>,<div>,pre,table gibi) yazılmadan önce bir satır boşluk bırakılmalıdır. Bunun sebebi, bence, hizalanabilen(layout) HTML elemanalarını diğer elemanlardan ayırmaktır.
  • Span-Level(inline) elemanlar olduğu gibi yazılabilir. Öncesinde bir satır boşluk bırakmaya gerek yoktur.
  • Line Break, yani <br \> için, satır sonunda 2 SPACE karakteri bırakılmalıdır.
  • Kod Blokları: Blog yazılarında bir işi gösteren HTML Kodu(sample) yazmak gerekebilir. Burada HTML elemanlarını olduğu gibi yazamayız. Çünkü, bu durumda browser, bu elemanları render eder. Bu yüzden bu yazıları, küçük bir html kodu için inline code block ya da büyük HTML kodları için <pre><code></code></pre> olarak render edilecek olan <code> bloğu olarak formatlamamız gerekir. İlki için, backtick‘ler kullanılır, ikincisi için; öncesinde bir satır boşluk bırakılır sonra o satırda 4 SPACE ya da 1 TAB indentation yapılır.
  • Escape Karakteri: Aslında yukarıdaki maddenin de sebebi olan;markdown‘ın syntax‘ını belirtmek için kendine ayırdığı noktalama işaretlerini, formatlama aracı olarak değil de bir noktalama işareti gibi kullanmak istediğimizde markdown‘a bunu belirtmemizin gerekmesi, Escape Karakterlerini oluşturmuştur. Escape karakteri, “\” dir.

Escape Karakteri aşağıdaki karakterler için kullanılabilir:

\ backslash 
` backtick 
* asterisk 
_ underscore 
{} curly braces 
[] square brackets 
() parentheses 
# hash mark 
+ plus sign 
- minus sign (hyphen) 
. dot 
! exclamation mark 

Yukarıdaki karakterlerin aslında markdown‘ın syntax‘ını oluşturan karakterler olduğunu da dolaylı olarak anlamış oluruz.

Markdown Syntax’ı – Block-Level: p, br, h1, blockquote, list, code

Paragraflar

Paragraf için <p></p> şeklinde belirtmeye gerek yoktur. Öncesinde bir satır boşluk olan yazı bloğu paragraf olarak algılanır.

Line Breaks

Satır sonunda 2 SPACE bırakılmalıdır.

Headers – <h1>,<h2>,<h3>...<h6>

Bir satırın altına miktarı önemli olmayan sayıda; “=” yazılırsa o satır <h1> olarak algılanır, “-” yazılırsa o satır <h2> olarak algılanır.

Bunlar haricinde # h1 - başlık1 # , ###### h6 - başlık ###### formatı da kullanılabilir.

Blockquote – Alıntılar

> alıntı buradan başlar... yapısı kullanılabilir. İç içe alıntılar ilk alıntı bloğunun altında >> şeklinde yazılarak yapılabilir. Alıntılar, diğer markdown elemanlarını içerebilir.

> dan sonra bir paragraf varsa, paragraf line break‘ler içeriyorsa, her satırın başına > koymaya gerek yoktur.

Lists – Listeler

Numaralandırılmamış listeler için * yapısı, numaralandırılmışlar için de 1. yapısı kullanılabilir.

Code Blocks – Kod Blokları

Bir satır 4 SPACE ya da 1 TAB içerden başladığında kod bloğu olmuş olur. Inline Code Block için “(backtick) kullanılır.

Markdown Syntax’ı – Span Level: em, b, a, img

Emphasis – İtalik – em

* ile <em>, ** ile de <b> etiketleri koyulmuş olur.

Link

[buraya tıklayınız](http://www.example.com "alt") şeklinde yazılabilir.

Otomatik linkler için, <www.example.com> yapısı kullanılabilir.

Ek – 11.09.2011 

Markdown, iki çeşit link‘i destekler: Inline ve Reference tiplerinde linkler.

Çok fazla link olduğunda, linkleri düzenlemek için en uygun yöntem olan Referans tipi linklerin kullanımı şöyledir:

Bu [şuraya][şurasının id‘si] bir link -> Bu şuraya bir link.

Düzenleme kolaylığı için de id ile belirtilen linkler, sayfanın en altında listelenir.

[id]: http://www.surasi.com

Ek – 11.09.2011

Image

![alternative text - alt](\resmin\yolu\resim.uzanti) yapısı kullanılır.

Markdown ile HTML’e Dönüştürme işlemi

Markdown, Perl çalış. dosyası indirildikten sonra,

lifeinbeta@lifeinbeta:~/Desktop$ perl Markdown.pl -html4tags markdown.txt >> 1.html 

şeklinde çağrılabilir.

NOT: Bu işi incremental hale getiren bir bash script‘i yazacağım.

Bu yazının markdown ile HTML’e dönüştürülmeden önceki halinden bazı bölümler:

*Markdown* Nedir? 
================= 
[Markdown Syntax'ta](http://daringfireball.net/projects/markdown/syntax) bahsedildiği gibi, 
> HTML Syntax'ının ufak bir altkümesidir. 
HTML Tag'larını eklemeyi kolaylaştırmak için düşünülmüştür. 
Bu bağlamda uzun blog yazılarını yazmayı kolaylaştırmak için kullanacağım. 
Yani, yazıyı baştan sona düz yazı olarak `gedit` ya da `vim`'de *markdown*'ın *syntax*'ı ile yazacağım. HTML bir *publishing*(yayın) formatıdır. 
... 
... 
* *Block-Level* HTML Elemanlarında, herhangi bir eleman(`<p>`,`<div>`,`pre`,`table` gibi) yazılmadan önce, bir satır boşluk bırakılmalıdır. *Markdown* boşluk olarak *TAB* ya da *SPACE*'i algılar. Bunun sebebi, bence, hizalanabilen(*layout*) elemanalarını diğer elemanlardan ayırmaktır. * *Line Break*, yani `<br \>` için satır sonunda iki adet boşluk karakterteri bırakılmalıdır. 
.... 

Birkaç Not

  1. Markdown context-sensitive‘dir diyebiliriz. HTML bir *publishing*(yayın) formatıdır. kısmındaki () işlaretleri, morkdown‘da tanımlı karakterler oldukları halde, kullanıldıkları yer geçerli oldukları context olmadığı için escape yapmak zorunda kalmadık.
  2. Code Block içerisinde parse işlemi yapılmaz. Bu nedenle istenen karakter escapeedilmeden yazılabilir.emphasis yazmak için, *emphasis* yazmaya gerek yok.
  3. Backtick kullanımı;`` yapısı içerdeki iki tane *backtick*'i *escape* eder.inline code block“ gibi.
  4. zzzzzzzzzzzzz

zzzzzzzzzzzz

Mesela, e-posta'larda kullanıldığı gibi; 

kısmından sonra bir satır boşluk bırakılmadan

*emphasis*, yani bir kelimenin etrafına * işaretleri koyulur, 

şeklinde devam edilirse code block başladığının farkına varmaz.

Ayrıca, Liste içerisinde <code> kullanımı sıkıntı yaratabilir. Listelerde, <code> ‘lar için zzzz yapısı kullanılmalıdır.

Bir Benzetme

Bu yazıyı yazarken aklıma bootstrapping compiler) kavramı geldi. Bu kavram, bir dilin compiler programının o dille yazılmasıyla ilgilidir (C Compiler’ının C’de yazılması gibi). Böyle bir şey yapılmasının temel nedeni, bir dilin olgunluk seviyesini(maturity), o dilin compiler programını yazarak ölçmektir. Ben markdown‘ı kullanmaya alışmak için markdown ile bir yazı yazmak istedim. Yazdığım yazı markdown hakkında olduğu için, bir bakıma Bootstrapping Markdown oldu 😛

NOTLAR

Markdown‘ile oluşturduğum HTML çıktısını WordPress‘e koyduğumda şunlar oldu:

  1. Markdown’da Heading‘ler için  “=” (h1) ve “-” (h2) kullandığım için başlıklar kullandığım temaya göre aşırı büyüktü. h1’leri h2 ve h2’leri de h3 yaptığımda en uygun hali oldu. Bu yüzden bundan sonra, Markdown‘da ## baslik2 ## ve ### baslik2 ### yapısını kullanmam gerekecek.
  2. Markdown‘ın <pre><code> diye biçimlendirdiği kod bloklarında alt alta görünen satırlar, WordPress’te tek satıra indirgeniyor. Zor bir hata! Şimdilik elle düzelttim, ama bu hatayı çözmem gerekecek. Aslında, sadece <pre> içine alınmış olsa bu temadaki gösterimde sorun olmuyor. <pre><code></code></pre> içindeki yazıları WordPress‘in HTML kısmına kopyaladığımda <code></code> bloğundaki her bir satır ayrı ayrı <code> tag‘ları arasına alınıyor. Bu yüzden alt alta olan satırlar yan yana görünüyor. Bunun için,
Markdown.pl‘de,
#$result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n";
$result = "\n\n<pre>" . $codeblock . "\n</pre>\n\n";
şeklinde değiştirdim.

NOT: Aşağıdaki gerçeklemeler kısmı 21.05.2011 tarihinde eklendi.

Markdown Gerçeklemeleri

  1. Markdown’ın çoğu dildeki uygulamalarına referanslar bruada
  2. PEG grameri kullanılarak yapılmış olan C gerçeklemesi
  3. C Gerçeklemesi – Tanoku/Upskirt #Basit ve sade bir gerçekleme
  4. Ruby Gerçeklemesi – BlueCloth
  5. Ruby Gerçeklemesi – Maruku
  6. Ruby Gerçeklemesi – RDiscount
  7. Ruby Gerçeklemesi – Kitabu #Ayrıca, PDF‘de üretebiliyor. Bunun için, Prince PDF Generator kütüphanesi kullanılıyor.
  8. PHP Gerçeklemesi – PHP Markdown
  9. JavaScript Gerçeklemesi – Markdown-js
  10. JavaScript Gerçeklemesi – showdown
  11. Java Gerçeklemesi – MarkdownJ #Basit ve sade görünüyor. Ayrıca, Ant script‘i var. Ayrıca, jsp uygulaması olarak yapılmış.

Linkler

Şu linkte Markdown deneme tahtası ve bunun hemen yanında syntax cheatsheet var.

1 Comment

Filed under araç

UNIX – MAN Sayfaları ile Dosya Okuma-Arama Komutları ve Ortam Değişkenleri


Ubuntu Software Center‘la ya da apt-get install ile Software Repo‘larından yüklediğim programların yüklendiği yerleri bulma konusunda hep sıkıntı yaşadım.

Aslında, Ubuntu‘da kullanıcının yüklediği dosyaların, File System üzerinde nereye yüklendiğini bir türlü bir yere not etmedim. Hala da emin değilim, yani hepsini adam akıllı bilmiyorum. Ama bu yazıya onları da ekleyeceğim.

Yüklediğim uygulamaların yerlerini bulmak için çeşitli command-line utility‘lerini üstün körü kullandım.(find, locate gibi) Ancak, gerek parametrelerini gerekse parametre yapılarını tam anlamıyla kullanamadım. Her defasında da Linux Commands diye aratmaktan sıkıldım. Çünkü, arama sonuçlarında üst sıralarda çıkan kaynakların büyük çoğunluğu, zaten bilinen genel komutlardan bahsediyor.

Sonunda aradığım komutun dpkg olduğunu buldum.

dpkg -S freeglut3

ile aratarak freeglut paketinin dosyalarının yerlerini bulabildim.

UNIX Programcılarının meşhur sözü:

Read the frickin’ man page!” hep aklımın bir köşesindeydi, ancak zahmet edip okumak istemedim(zaten belirtilen cümlede de biraz zahmet etmek gerektiği vurgulanıyor).

Neyse, bu kadar giriş(prologue) yeter. 🙂

MAN sistemini detaylı bir şekilde inceleyelim.

MAN Sistemi Neden Var?

Konuya, Speaking UNIX: Man oh Man yazısını açarak başlayalım.

UNIX Komut Satırı’nda yazılabilecek 1500’den fazla komut vardır. Bu komutların birçok parametresi(option) vardır. Ayrıca, yeni eklenen programların çalıştırılabilir dosyaları da hesaba katıldığında, bu komutların her birini, her bir parametresini, belli bir işi yapmak için gerekli parametre kombinasyonunu hatırlamak çok zordur.

İşin kötüsü, UNIX komutları 40 yılı aşkın süredir var ve komut yapısını belli kategorilere ayırmak zor. Mesela, dosya sistemi üzerinde işlem yapma komutu olan ls nin -l parametresi long list(uzun liste) anlamında iken diğer dosya sistemi komutlarında aynı anlama gelmez. Ayrıca bu parametre hiç olmayabilir de. Komutlar buna çözüm olarak --help parametresi ile komutun bir özetini sağlarlar ama çoğu zaman bu özet bilgi, yapılmak istenen işi yapmakta yeterli olmaz.

Daha önceden bir işi yapmak için kullanılan komutun yapısını hatırlama problemini aşmak için birkaç yol, çok kullanılan komutlara parametreleri ile beraber bir alias vermek ya da belli komutları bir shell script‘inde toplamak olabilirdi. Ancak, bu da çoğu zaman yeterli olmaz ki öncelikle daha alias oluşturmadan o komutu yazabilmek gerekir.

Tüm bu problemlerden dolayı, UNIX online referans sistemi yapılmıştır.

Man Sistemi Nasıldır?

UNIX Man Sistemi, iki kısımdan oluşur. Birincisi, online referans sistemi ikincisi de bu sistemdeki referans sayfalarını okuyabilen bir okuyucudur. Sayfalara man page denir. Okuyucu program ise man komutuyla çağrılan yazılım parçasıdır.

man cd

man dpkg

man man

ile herhangi bir programın man sayfası okunabilir.

Yüklü her UNIX programı(daha doğrusu yazılım parçası, program harici UNIX yazılım bileşenlerinin de man sayfaları vardır), kendi man sayfasını da bulundurur.

Programların man sayfalarının bulunup okunacağı yerler(repo)

/usr/man

/usr/share/man

ya da bazen programın kurulum dizininin, mesela MySQL’de,

/usr/local/mysql/man

altında oluşturulan dosyalarda belirtilir.

lifeinbeta@lifeinbeta:/usr/share/man$ cd tr
lifeinbeta@lifeinbeta:/usr/share/man/tr$ ls
man1 man5 man8
lifeinbeta@lifeinbeta:/usr/share/man/tr$ cd man1
lifeinbeta@lifeinbeta:/usr/share/man/tr/man1$ ls
chage.1.gz chfn.1.gz login.1.gz passwd.1.gz su.1.gz
lifeinbeta@lifeinbeta:/usr/share/man/tr/man1$ cd ..
lifeinbeta@lifeinbeta:/usr/share/man/tr$ cd man5
lifeinbeta@lifeinbeta:/usr/share/man/tr/man5$ ls
passwd.5.gz shadow.5.gz
lifeinbeta@lifeinbeta:/usr/share/man/tr/man5$ cd ..
lifeinbeta@lifeinbeta:/usr/share/man/tr$ cd man8
lifeinbeta@lifeinbeta:/usr/share/man/tr/man8$ ls
groupadd.8.gz groupmod.8.gz useradd.8.gz usermod.8.gz
groupdel.8.gz synaptic.8.gz userdel.8.gz
lifeinbeta@lifeinbeta:/usr/share/man/tr/man8$

Yukarıda görüldüğü gibi, her repo çeşitli alt kısımlara(man1, man2, … ,man8) bölünmüştür. Bunların herbiri bir komut kategorisini belirtir. Böyle bir şeye ihtiyaç duyulmasının nedeni, komutları bir namespace ile ilişkilendirmektir. UNIX’te bir sürü yazılım parçası olduğu için yeni oluşturulan bir yazılım parçasına verilecek ismin diğerlerinden birininin ismiyle çakışma ihtimali vardır. Mesela, iki farklı crontab komut vardır. 1. bölümde(man1) açıklaması bulunan crontab, Job Scheduling için kullanılılan bir programdır. 5. Bölümdeki crontab ise bir dosya formatıdır. Hangi crontab‘ın man sayfasına bakılmak istendiği aşağıdaki gibi belirtilir:

$ man 1 crontab
$ man 5 crontab

Bir MAN Sayfasının Anatomisi

Man Sayfalarının yapıları birbirine çok benzer. Bu yapı bir gelenek haline gelmiştir. Bu nedenle, man sayfalarını okumaya alıştıktan sonra okumak zevkli(!) hale gelebilmektedir.

Bir man sayfası en az, şu beş bölümden oluşur; name(isim), synopsis(özet), description(açıklama), examples(birkaç örnek), see also(bu komutla ilişkili diğer komutlara referanslar). Şimdi bunları açıklayalım:

  • name: yazılım parçasının(program, fonksiyon ya da dosya formatı) adı ve ne işe yaradığını belirten tek satırlık açıklama
  • synopsis: yazılımın nasıl kullanılabileceği konusunda bir özet sağlar. Eğer man sayfası bir çalıştırılabilir dosyanın ise, onun mandatory(zorunlu) ve optional(zorunlu değil) parametrelerini(option ya da switch), parametre formatı ve parametre sırasını belirtir. Eğer sayfa bir sistem çağrısı ya da bir kütüphane fonksiyonun ise, fonksiyonun parametrelerini ve header dosyası gerekiyorsa o fonksiyonun hangi header‘ı kullandığını belirtir.
  • description: Bu kısımda, komutun işlevinin açıklaması vardır. [OPTIONS] kısmında belirtilen parametrelerin ne iş yaptıkları açıklanır.

du komutunun;

BSD UNIX‘teki synopsis‘i,
du [-H | -L | -P] [-a | -s | -d depth] [-c] [-h | -k | -m | -g] [-x]
[-I mask] [file ...]
şeklindedir.
Burada, du komutundan sonra istenirse(optionally) -H, -L, -P parametrelerinden herhangi biri girilebilir.
Daha sonra -a, -s, -d parametrelerinden herhangi biri girilebilir.
Eğer -d parametresi girilmişse argüman belirtilmelidir.
Daha sonra -c parametresi girilebilir.
Bundan sonra -h, -k, -m ya da -g den biri girilebilir ya da hiçbiri girilmeyebilir.
Sonraki parametrelerden biri girilecekse devam edilir.
-x parametresi girilebilir ya da girilmeyebilir.
Daha sonra -I parametresi girilirse yanına argümanı belirtilmelidir.
Daha sonra dosya ad(lar)ı belirtilir.
Buradaki “...” birden fazla dosya girilebileceğini gösterir.
Burada parametre sırası önemli değildir.
Bu komut doğrudan du file1 gibi girilebilir.

Özetle, parametre grupları checkbox gibi düşünülür. Parametre grubundaki parametreler ise radio butonları gibidir.

Ubuntu’da synopsis, BSD UNIX’te gösterildiği gibi gösterilmez:

DU(1) User Commands DU(1)

NAME
du - estimate file space usage

SYNOPSIS
du [OPTION]... [FILE]...
du [OPTION]... --files0-from=F
DESCRIPTION
Summarize disk usage of each FILE, recursively for directories.

Mandatory arguments to long options are mandatory for short options
too.

-a, --all
write counts for all files, not just directories
şeklinde gösterilir. Synopsis‘te komut yapısın  [OPTIONS] ile belirtilir. Bu kısımda belirtilen parametreler, DESCRIPTION kısmındaki açıklamadan sonra açıklanır.

GNU UNIX Komutlarının Parametre Yapısı

UNIX komutlarında parametreler case-sensitive‘dir. Yukarıda görüldüğü gibi hem ayrı işleri yapan -H hem de -h parametresi vardır.
UNIX’te parametreler tek harfliyse komutta du -a -H şeklinde ayrı ayrı belirtilebileceği gibi, -Ha şeklinde birleştirilebilir.
Parametre bir kelime ise(Yukarıdaki man sayfasında -a, --all şeklinde belirtildiği gibi) parametrenin başına “--” konur.
Birden fazla kelimeden oluşuyorsa, --apparent-size da olduğu gibi, kelimeler arasına “-” konur.
NOT: Bazı programlar(find -name 'core' gibi) bu yapıya uymayabilir.
Daha detaylı açıklama için buraya bakılabilir.

Parametre yapısı konusunda şurada bazı genel UNIX parametrelerinin anlamları var. Bu özetin daha genel hali ise Art of UNIX Programming – Eric Raymond kitabının bir bölümünün anlatıldığı yer olan burada.

Environment

Man Sayfalarında mutlaka olan bu beş kısım haricinde en çok Environment kısmı bulunur.
lifeinbeta@lifeinbeta:~$ man man
...
ENVIRONMENT
MANPATH
If $MANPATH is set, its value is used as the path to search for
manual pages.

MANROFFOPT
The contents of $MANROFFOPT are added to the command line every
time man invokes the formatter (nroff, troff, or groff).

MANROFFSEQ
If $MANROFFSEQ is set, its value is used to determine the set of
preprocessors to pass each manual page through. The default
preprocessor list is system dependent.

MANSECT
If $MANSECT is set, its value is a colon-delimited list of sec‐
tions and it is used to determine which manual sections to
search and in what order.

Bu kısımda bu komuta ait Environment Variables(Ortam Değişkenleri) belirtilir.

Ortam Değişkenleri

UNIX Komutları olan; envset ve printenv ile bütün ortam değişkenleri ve değerleri görülebilir. Burada env ve set ortam değişkenlerine değer atamak için de kullanılabilir. printenv ‘e argüman olarak bir ortam değişkeni verildiğinde onun değeri görülebilir. Bir shell açık iken herhangi bir ortam değişkenine değer atandığında o değerin bütün sistemde geçerli olabilmesi için (ortam değişkenleri, çalışan bir shell process‘ine göredir, yani global değil yereldir. Mesela, bir terminal açıkken bir shell process‘i oluşturulur. Başka bir terminal açıldığında farklı bir shell process‘i oluşturulur. Bu yüzden bir shell’de değiştirdiğimiz ortam değişkeninin değeri, diğerinde geçerli olmaz.)

Daha teknik olarak ifade etmek gerekirse,

UNIX, parent process tarafından oluşturulmuş bütün process‘lere bilgi aktarmak için Ortam Değişkenlerini kullanan bir mekanizmaya sahiptir. Sisteme giriş yapıldığında, önceden tanımlı belli sayıda değişken(varsayılan ortam değişkenleri: PATH, HOME vs.) otomatik olarak sağlanır. Bu listeye yeni bir Ortam Değişkenini ve değerini eklemek için shell start up dosyaları kullanılabilir.

lifeinbeta@lifeinbeta:~$ cat .bashrc
...
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
...
# TAHA YAVUZ BODUR WROTE:
export JAVA_HOME=/usr/lib/jvm/java-6-openjdk
export PATH=$PATH:$JAVA_HOME/bin:~/play-1.1.1

Çalışan her child process ortam değişkenlerini ve değerlerini inherit  eder. Parent Processten child process’lere bilgi akışı tek yönlüdür. Bunun sebebi, her child process‘in kendi kafasına göre Ortam Değişkenlerini değiştirmesini engellemektir. Böyle bir durumda kimin neyi değiştirdiği bilinemezdi. Bu nedenle, bir child process bir ortam değişkenini değiştirmek istediğinde, ortam değişkeninin değerini değiştirdikten sonra aşağıdaki gibi,

export VARIABLE=value  # Bourne, bash, ve bunlar gibi shell'ler için.

bu değişikliği sisteme bildirir. Artık, bütün child process‘ler bu yeni değerleri kullanabilir.

Bununla birlikte, eğer yukarıdaki komut doğrudan açık terminal’den girilirse, çalışan bu terminal açıldığında oluşturulmuş olan shell process‘i üzerinden işletileceği için, yine bu shell‘de daha sonra çalıştırılacak olan yeni komutlar ortam değişkenin yeni değerini görebilir. Ancak, bu komut ayrı bir shell script‘i olan .bashrc içine yazıldığında, değiştirilen değeri güncelleyebilmek için bu script‘in çalıştırılması gerekir. Bu yüzden, UNIX Command Execution kısmında belirtildiği gibi, source .bashrc komutu çalıştırılmalıdır.

NOT: Shell bir interpreter‘dır(yorumlayıcı). Bu program, yeni bir komut girildiğinde, o komutun çalıştırılabilir dosyasını PATH‘de belirtilen dizinlerde arar. /usr/bin dizininde(built-in shell komutlarının bulunduğu dizin) bulur. Bulduğunda, shell process‘i(parent process) tarafından fork edilir. Yani, bir child process oluşturulur.

Ayrıca, yukarıda belirttiğim komutların çıktıları aşağıdaki gibidir:

lifeinbeta@lifeinbeta:~$ env
ORBIT_SOCKETDIR=/tmp/orbit-lifeinbeta
SSH_AGENT_PID=1368
TERM=xterm
SHELL=/bin/bash
...
DESKTOP_SESSION=gnome
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/lib/jvm/java-6-openjdk/bin:/home/lifeinbeta/play-1.1.1
PWD=/home/lifeinbeta
JAVA_HOME=/usr/lib/jvm/java-6-openjdk
GDM_KEYBOARD_LAYOUT=tr
...
HOME=/home/lifeinbeta
...

Man sayfalarında navigasyon

man komutunun man sayfası çok uzundur(706 satır). Bu yüzden bütün açıklamayı okumak zordur. Bunun için man sayfalarını etkili bir şekilde kullanabilmek gerekir.

UNIX’te bir dosyanın içeriğini standard output‘ta(stdout, standard input -> stdin) göstermek için; cat,more,less,head ve tail kullanılabilir.

cat

cat, parametre olarak belirtilen dosya ya da dosyaların tüm içeriklerini arka arkaya standart output‘a yazar.
lifeinbeta@lifeinbeta:~/CSWORKS/C$ cat dummy1.c dummy2.c
#include <stdio.h>

int main() {
printf("Hello cruel world\n","%s");
return 0;
}
#include <stdio.h>

int main() {
printf("Shared library'nin ikinci .o bilesenini oluşturacak.");
return 0;
}

Ayrıca, cat’ta redirection işlemleri yapılabilir.

lifeinbeta@lifeinbeta:~/CSWORKS/C$ cat dummy1.c dummy2.c >> dummy_rm.c
lifeinbeta@lifeinbeta:~/CSWORKS/C$ cat dummy_rm.c
#include <stdio.h>

int main() {
printf("Hello cruel world\n","%s");
return 0;
}
#include <stdio.h>

int main() {
printf("Shared library'nin ikinci .o bilesenini oluşturacak.");
return 0;
}
Burada, dummy1.c ve dummy2.c nin stdout‘a yazılan verileri redirection operatörü(>>) ile dummy_rm.c’nin stdin‘ine aktarılır, stdin‘deki bu veriler dummy_rm.c‘ye yazılır.
cat, belirtilen bütün dosyalardaki tüm içeriği, en son dosyanın EOF‘ı ile karşılaşıncaya kadar stdout‘a yazar. Bu nedenle uzun içerikli dosyalarda kullanışlı değildir.
Uzun sayfalarda hareket ederken more ve less gibi sayfalama komutları kullanılabilir.

More ve Less Sayfalama Komutları

more, dosya içeriğini, cat gibi, stdout‘a bir defada alır ve belli uzunlukta bölümler halinde terminalde gösterir.
lifeinbeta@lifeinbeta:~/CSWORKS/helloworld/public/javascripts$ more jquery-1.4.2.min.js
/*!
* jQuery JavaScript Library v1.4.2
* http://jquery.com/
...
--More--(1%)
şeklinde, toplam sayfa boyutunun ne kadarın gösterildiği(1%) yazar.

less, more ve cat‘e göre hızlıdır. Çünkü, dosyadaki tüm veriyi birden stdout‘a almaz. O anda açık olan terminale sığdırabildiği kadar veriyi stdout‘a yazar. Aşağıdaki hareket tuşlarından herhangi birine bastıkça tuşun ilerleme miktarı kadar veriyi stdout‘a yazar.

Navigasyon Tuşları

Bölümler arasında hareket ederken,

ENTER, bir satır alta geçer.
SPACE, bir sonraki bölüme geçer.
CTRL-), 8 Karakter sağa gider.
CTRL-(, 8 Karakter sağa gider.
CTRL-B, bir önceki bölüme döner.
y, bir satır geri gelir.
b, bir terminal boyu kadar geri gelir.
[:10g], 10 satır geri gelir. (ex, vi, vim‘deki komut yapısı gibi.)
[/synopsis], “synopsis” kelimesini bulur ve oraya odaklanır.

NOT: “/” ile ileriye doğru arama yapılır. “?” ile geriye doğru arama yapılır. Arda arda arama(incremental search) imkanı sağlamaz.

[:e diger_dosya], görüntülenen dosyadan çıkıp diğer dosyayı görüntüler.
q ya da CTRL^Z, less‘ten çıkar.

Özetle, uzun man sayfaları için less çok kullanışlıdır.

Örn:
man man | less

NOT: buradaki “|”(pipe) ile, man komutunun stdout‘a yazdığı man sayfası, more komutunun stdin‘ine yazılır. Bu sayede, less komutu bu veriyi gösterir.

head ve tail

Bu komutlar, sırasıyla baştan ya da sondan, argüman olarak belirtilen dosyalardan, -n argümanı ile belirtilen(n, okunmak istenen satır sayısını gösterir, varsayılan olarak 10’dur.) belirtilen sayı kadar satırı stdout‘a yazar.
tail ile ayrıca, eğer hali biz baktığımız sırada yazılan bir dosya varsa, dosyaya yazılanları takip etmek için -f  parametresi(follow) ile kullanılabilir.
tail -f /program/log/dosyası

Man sistemini daha iyi kullanmak için

  1. man komutunun Environment Variable‘larından biri olan MANPATH, tıpkı shell‘in bir Environment Variable‘ı olan PATH gibi, bir dizin listesi tutar. PATH‘in tuttuğu dizin listesi, shell‘den çağrılabilecek çalıştırılabilir dosyalarının yerlerini içerir. MANPATH de man sayflarının bulunabileceği dizinleri belirtir. Man sayfalarının konumlarının genelde belli bir yer olduğunu ama bazı programların man dosyalarını kendi dizinleri içinde tutabileceğini söylemiştim.
  2. Man sayfası, aradığımız yazılım parçasının tam adını bilmiyorsak, man repo‘larından man -k aranacak_seyin_kalibi ile aratılabilir.
  3. Varsayılan yerinde olmayan, programın kurulum dizininde olan bir man sayfasını okurken man ./manfile ile okumalıyız. Yani, ./ ile bulunduğumuz dizindeki manfile diye özellikle belirtmeliyiz. Eğer man manfile yazarsak, bütün MANPATH‘i tarar. Eğer belirttiğimiz dizin MANPATH‘e eklenmemişse de bu man dosyasını bulamaz.
  4. MANPATH’in mevcut değerine,
lifeinbeta@lifeinbeta:~/CSWORKS/C$ echo $MANPATH

lifeinbeta@lifeinbeta:~/CSWORKS/C$ manpath
/usr/local/man:/usr/local/share/man:/usr/share/man:/usr/lib/jvm/java-6-openjdk/man
ile bakılabilir.

Şimdi, gelelim asıl mevzuya:

Man sayfalarını bu kadar detaylı öğrenmek elde edeceğim kazanca değecek mi diye düşündüm. Tam emin değilim 🙂 Ancak, benim için asıl önemli olan UNIX üzerinde aradığım şeyi kolayca bulabilmekti. Bu yüzden;

UNIX’te Aranan Bir Şeyi Bulabilmek

Öncelikle which ve find kullanılır.
Ayrıca, Debian Package Manager bulunan Linux sistemlerinde dpkg kullanılabilir.
Şimdi artık bu çalıştırılıabilir dosyaların man sayfalarını inceleyebiliriz:

which – Çalıştırılabilir Dosyları Bulmak

Bulmak istediğimiz çalıştırılabilir dosya(lar)ın ad(lar)ını biliyorsak,
lifeinbeta@lifeinbeta:~$ which java exaile chromium-browser
/usr/bin/java
/usr/bin/exaile
/usr/bin/chromium-browser
yazılır. Bu komutta dosyanın mutlaka tam adı belirtilmelidir.

find

Bu komut ile wildcards ve regular expressions kullanarak, belirttiğimiz dosyanın dosya sistemindeki root‘undan başlayarak tüm sistemde arama yapabiliriz.

Man Sayfası:

FIND(1) FIND(1)

NAME
find - search for files in a directory hierarchy

SYNOPSIS
find [-H] [-L] [-P] [-D debugopts] [-Olevel] [path...] [expression]

DESCRIPTION
This manual page documents the GNU version of find. ...
...
EXPRESSIONS
The expression is made up of options ..

OPTIONS
All options always return true.

TESTS
...
-executable
Matches files which are executable and directories which are searchable

-name pattern
Base of file name 
(the path with the leading directories removed) 
matches shell pattern pattern. 
The metacharacters (`*', `?', and `[]') match a `.' 
at the start of the base name 
(this is a change in findu‐
tils-4.2.2; see section STANDARDS CONFORMANCE below). ... 
The filename matching is performed with the use of the fnmatch(3) library function. 
Don't forget to enclose the pattern in quotes in order to protect it from expansion by the shell.

-path pattern
File name matches shell pattern pattern. The metacharacters do not treat `/' or `.' specially; so, for
example,
find . -path "./sr*sc"
will print an entry for a directory called `./src/misc' (if one exists). To ignore a whole directory tree,
use -prune rather than checking every file in the tree.

-readable
Matches files which are readable. ...

-regex pattern
File name matches regular expression pattern. This is a match on the whole path, not a search. For exam‐
ple, to match a file named `./fubar3', you can use the regular expression `.*bar.' or `.*b.*3', but not
`f.*r3'. The regular expressions understood by find are by default Emacs Regular Expressions, but this can
be changed with the -regextype option.

ACTIONS

-delete
Delete files; true if removal succeeded. ...

-exec command ;
Execute command; true if 0 status is returned.

OPERATORS

Listed in order of decreasing precedence:

( expr )
Force precedence. Since parentheses are special to the shell, you will normally need to quote them. Many
of the examples in this manual page use backslashes for this purpose: `\(...\)' instead of `(...)'.

! expr True if expr is false. This character will also usually need protection from interpretation by the shell.

-not expr
Same as ! expr, but not POSIX compliant.

expr1 expr2
Two expressions in a row are taken to be joined with an implied "and"; expr2 is not evaluated if expr1 is
false.

expr1 -a expr2
Same as expr1 expr2.

expr1 -and expr2
Same as expr1 expr2, but not POSIX compliant.
şeklinde uzayıp gider. Tüm man sayfasını buraya yazmadan bitirdiğim iyi oldu 🙂

Örnekler

Synopsis‘te belirtilen komut yapısından hareket edersek;

  1. find / stdio.h gibi bir şey yazarsak 🙂 birçok ! sonuç dönecektir. Yani, find’ın belirttiğimiz yapıyla, belirtilen dizindeki dosya isimlerini değil, bu dizin ve onun alt dizinlerindeki dosyaların içlerinde “stdio.h” karakterlerini aradığını anlarız.
  2. Sadece dosya ismine göre, dosya içerisinde arama yapmadan, arama yapmak istersek,
    lifeinbeta@lifeinbeta:~$ find / -name "stdio.h"
    ...
    find: `/tmp/.esd-114': Permission denied
    find: `/root': Permission denied
    /usr/include/bits/stdio.h
    /usr/include/c++/4.4/tr1/stdio.h
    /usr/include/stdio.h
    find: `/var/cache/ldconfig': Permission denied
    find: `/var/log/samba/cores': Permission denied
    ...
    ^Z
    [2]+  Stopped                 find / -name "stdio.h"
    lifeinbeta@lifeinbeta:~$ sudo find / -name "stdio.h"
    [sudo] password for lifeinbeta:
    /usr/include/bits/stdio.h
    /usr/include/c++/4.4/tr1/stdio.h
    /usr/include/stdio.h

    yapabiliriz. Synopsis‘te belirtilen [expression] kısmına, -name "stdio.h" yazdık. İlk durumda Permission Denied hataları verdi. Bunları önlemek için, sudo ile superuser olarak ikinci defa aynı komutu çalıştırdık.

  3. [expression] kısmındaki parametreleri yazmaya devam edebiliriz. -ls ile bulunan sonuçları tablo yapısı halinde gösterebiliriz.
    lifeinbeta@lifeinbeta:~$ sudo find / -name "stdio.h" -ls
    170601    8 -rw-r--r--   1 root     root         5654 Jan 22 01:51 /usr/include/bits/stdio.h
    780739    4 -rw-r--r--   1 root     root         1210 Mar 27  2010 /usr/include/c++/4.4/tr1/stdio.h
    197965   32 -rw-r--r--   1 root     root        31109 Jan 22 01:51 /usr/include/stdio.h

    şeklinde.

  4. Man Sayfasında OPERATORSkısmındaki açıklamadan gidersek,
    lifeinbeta@lifeinbeta:~$ sudo find / \(-name '*.o' -o -name '*.h' \) -print
    find: invalid expression; you have used a binary operator '-o' with nothing before it.
    lifeinbeta@lifeinbeta:~$ sudo find / \( -name '*.o' -o -name '*.h' \) -print
    ...
    /home/lifeinbeta/Desktop/Samples/jpgtn/src/common.h
    /home/lifeinbeta/Desktop/Samples/jpgtn/src/writejpeg.o
    /home/lifeinbeta/Desktop/Samples/jpgtn/src/jpgtn.h

    şeklinde bir işlem yapabiliriz. Burada ilk denemede hata vermesinin sebebi,         \(-name ... ifadesinde, \( den sonra bir boşluk karakteri olmamasıydı. Neyseki man sayfasına bakarak bu hatayı düzelttim.

locate

Herhangi bir dosyayı bulmak için kullanılır. Dosya adı doğrudan yazıldığında, aranan dosya adının, dosya adlarının herhangi bir yerinde geçip geçmediğine bakılır. Regular Expressions ile dosya isimleri filtrelenebilir.

Man Sayfası

locate(1)                                                            locate(1)

NAME
       locate - find files by name

SYNOPSIS
       locate [OPTION]... PATTERN...
DESCRIPTION
       locate  reads  one or more databases prepared by updatedb(8) and writes
       file names matching at least one of the PATTERNs  to  standard  output,
       one per line.

       If  --regex is not specified, PATTERNs can contain globbing characters.
       If any PATTERN contains no globbing characters, locate  behaves  as  if
       the pattern were *PATTERN*.
...
OPTIONS
       -b, --basename
              Match  only  the base name ....
       ....
       -r, --regexp REGEXP
              Search for a basic regexp REGEXP.  No PATTERNs  are  allowed  if
              this  option  is used, but this option can be specified multiple
              times.

       --regex
              Interpret all PATTERNs as extended regexps.

       -s, --stdio
              Ignored, for compatibility with BSD and GNU locate.
...

şeklindedir.

Örnekler

lifeinbeta@lifeinbeta:~$ locate stdio.h
/opt/lampp/lib/perl5/5.10.1/i686-linux/CORE/nostdio.h
/usr/include/stdio.h
/usr/include/bits/stdio.h
/usr/include/c++/4.4/tr1/stdio.h
/usr/lib/perl/5.10.1/CORE/nostdio.h
lifeinbeta@lifeinbeta:~$ locate -r stdio.*
...
/usr/lib/python2.6/dist-packages/twisted/test/test_stdio.py
/usr/lib/python2.6/dist-packages/twisted/test/test_stdio.pyc
...

şeklindedir.


Linkler

Çok Kullanılan UNIX Komutları

  1. Ten Terminal Commands That Will Boost Your Productivity
UNIX Komut Yapısı
  1. What does {some strange unix command name} stand for?
  2. Which command line commands style do you prefer? – Stackoverflow Polls
Ortam Değişkenleri
  1. PATH ve CLASSPATH ve Java
  2. C ile Ortam Değişkenleri Üzerinde İşlemler
  3. Wiki – Environtment Variables #Ortam Değişkenlerinin hepsi nasıl görülebilir? #Ortam değişkenleri nasıl set edilir? #Bir ortam değişkeninin değeri nasıl okunur?
Shell
  1. Grymoire – UNIX  #Shell #Oldukça kapsamlı referans özellikleri de taşıyan bir Tutorial
  2. Steve Parker Shell Tutorials  #Shell #Daha çok uygulamalı bir Tutorial
  3. Bash Reference Manual #Detaylı, Özellikle Shell İçin bütün açıklamalar
  4. Bash Programming HOWTO #Detaylı
  5. Shell Script’leri ile GUI uygulamaları yapmak #Eğlence
  6. Exit ve Exit Status #Komutların exit status’leri
  7. Bash Koşul İfadeleri  #Detaylı
  8. Bash Conditionals #Sade
  9. Bash Cheatsheet
  10. Çok kullanılan Bash Built-in Komutları
  11. Shell – Command Execution and Simplest Scripts
UNIX Regex
  1. UNIX Regular Expressions – Grymoire
Diğer
  1. UNIX Programming FAQ
  2. GNU All Documentation #GNU Utility‘lerinin Dökümantasyonu

NOT: Bu yazıyı yazarken öğrendiğim şeylerden biri bundan sonra markdown‘ı kullanmam gerektiği oldu.

1 Comment

Filed under UNIX

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