Çalışma Zamanında Kütüphanelerin Aranma Sırası

Çalışma zamanında kütüphanelerin aranmasında, derleme zamanına görece, daha karmaşık bir yol izlenmektedir. İlk olarak kütüphanelerin aranmasıyla ilgili, derleme sürecini de ilgilendiren, bir özellikten bahsedelim.

Arama Dizinlerinin ELF Dosya İçeriğine Yazılması

Çalışma zamanında kütüphanelerin, standart arama dizinlerine ek olarak, nerelerde aranacağı ELF dosya içeriğine yazılabilmektedir. Çalışabilir ve paylaşımlı kütüphane dosyalarının her ikisinde de bu özellik kullanılabilmektedir.

Gerekli kütüphanelerin standart aranacak dizinlerde olmadığı ve konumlarının bilindiği durumlarda bu özellik kullanışlı olabilmektedir. Bağlayıcı bu amaçla rpath seçeneğini barındırmaktadır. Basit bir örnek üzerinden bu özelliği inceleyelim. Uygulama ve kütüphane kodlarını sırasıyla driver.c ve test.c olarak isimlendirelim.

void foo();

int main() {
    foo();
    return 0;
}
void foo() {
    puts(__func__);
}

İlk olarak, bu zamana kadar yaptığımız gibi kütüphaneye ilişkin herhangi bir arama bilgisi kullanmaksızın dosyaları derleyelim.

$ gcc -fPIC -shared -olibtest.so test.c
$ gcc -odriver driver.c -L. -ltest

Uygulamayı çalıştırdığımızda, dinamik bağlayıcı tarafından, aşağıdaki gibi bir hata almaktayız.

$./driver

./driver: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory

Dinamik bağlayıcı, driver uygulamasının bağımlılık listesinde bulunan libtest.so dosyasını, öntanımlı baktığı dizinlerde, bulamadığından uygulamayı çalıştıramamaktadır. Dinamik bağlayıcı tarafından uygulamanın çalıştığı dizine bakılmamaktadır. Standart dışı dizinlerde bulunan kütüphanelerin dinamik bağlayıcıya, daha pratik olarak, nasıl gösterilebileceğine bakacağız, fakat bu noktada bu işlemin rpath ile nasıl yapılabildiğine bakalım.

Test amaçlı olarak çalışma dizinimizde lib adıyla bir dizin oluşturup, libtest.so dosyasını bu dizine kopyalayalım.

$ mkdir lib
$ cp libtest.so lib

Uygulamamızı bu sefer kütüphaneye ilişkin arama dizin bilgisiyle beraber derleyelim.

$ gcc -odriver driver.c -Llib -ltest -Wl,-rpath,$PWD/lib

Not: PWD çevre değişkeni çalışılan dizini göstermektedir.

Bu sefer dinamik bağlayıcı libtest.so dosyasını bulmakta ve uygulamayı çalıştırabilmektedir.

$ ./driver
foo

Bu noktada derleme işleminde hem L hem de rpath seçeneklerine lib değerini geçirdik, fakat bu değerler birbirinden farklı olabilirdi. L seçeneği geliştirme ortamına rpath ise çalışma ortamına ilişkindir.

Uygulama veya kütüphane dosyalarının rpath listeleri ELF içinde DT_RPATH isimli bir alanda tutulmaktadır, bu bilgiye aşağıdaki gibi ulaşmak mümkündür.

$ readelf -d driver | grep PATH
 0x000000000000000f (RPATH)              Library rpath: [/home/serkan/embedded/so/test/29/lib]

Dinamik bağlayıcı kütüphaneleri ararken, ilk olarak rpath listesindeki dizinlere bakmaktadır. Bağlayıcının bu davranışı değiştirilebilmesine karşın, bu özellik her zaman kullanışlı olmamaktadır. Test ortamlarında standart dışı dizinlerde bulunan kütüphaneler dinamik bağlayıcıya gösterilmek istenmektedir, rpath bu yöntemin kullanılmasını engellemektedir. Bu yüzden ELF formatına önceliği daha düşük DT_RUNPATH isimli bir alan eklenmiştir. rpath olarak bu alanın kullanılabilmesi için bağlayıcıya --enable-new-dtags seçeneği geçirilmelidir.

Not: Bağlayıcıya --inhibit-rpath seçeneği geçirilerek, rpath listesine bakması engellenebilir.

Aynı uygulamayı bu şekilde tekrar derleyelim.

$ gcc -odriver driver.c -Llib -ltest -Wl,-rpath,$PWD/lib -Wl,--enable-new-dtags

Bu durumda ELF içinde DT_RUNPATH alanının kullanıldığını görmekteyiz.

$ readelf -d driver | grep PATH
 0x000000000000001d (RUNPATH)            Library runpath: [/home/serkan/embedded/so/test/29/lib]

rpath dizinleri mutlak olmak zorunda değildir, driver uygulamasını bu kez yalnız lib dizinini göstererek derleyelim.

$ gcc -odriver driver.c -Llib -ltest -Wl,-rpath,lib
$ readelf -d driver | grep PATH
 0x000000000000000f (RPATH)              Library rpath: [lib]

$ ./driver
foo

Uygulamanın çalıştığı dizinde lib dizini bulunduğu için bir problem çıkmayacaktır. Şimdi çalışma dizinimizde test isimli bir dizin oluşturalım ve uygulamayı bu dizindeyken çalıştırmayı deneyelim.

$ mkdir test
$ cd test
$ ../driver
../driver: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory

Dinamik bağlayıcı çalışma dizininde lib dizinini bulamadığı için beklediğimiz üzere hata vermektedir. Dinamik bağlayıcının, göreli yol ifadelerini çözümlerken, çalışma dizini yerine uygulamanın bulunduğu dizine bakması sağlanabilir. Bunun için $ORIGIN özel değeri kullanılmaktadır. $ORIGIN değeri, dinamik bağlayıcı tarafından, uygulamanın veya paylaşımlı kütüphanenin bulunduğu dizin ifadesine genişletilir. Tekrar çalışma dizinine geçerek örneğimizi aşağıdaki gibi tekrar derleyelim. Sonrasında test dizinine geçip tekrar çalıştıralım.

$ cd ..
$ gcc -odriver driver.c -Llib -ltest -Wl,-rpath,'$ORIGIN'/lib
$ cd test
$ ../driver
foo

Uygulamanın bu kez sorunsuz çalıştığını görmekteyiz. Bu özellik uygulamanın gerekli kütüphanelerle beraber dağıtıldığı durumlarda oldukça işe yaramaktadır. Örneğin, uygulama ve gerekli kütüphaneler arşivlendikten sonra hedef sistemde istenilen bir dizine açılarak çalıştırılabilir. Hedef sistemde kütüphaneler herhangi başka bir yere kopyalanmak zorunda değildir, ayrıca uygulamanın bulunduğu dizin PATH çevre değişkenine eklenerek, uygulama herhangi bir dizinden çalıştırılabilir.

Not: rpath seçeneğine alternatif olarak, önceliği daha düşük olan, LD_RUN_PATH çevre değişkeni kullanılabilir.

Standart Dışı Dizinlerde Arama

Standart dışı dizinlerin dinamik bağlayıcıya gösterilmesi gerekmektedir. Bu amaçla LD_LIBRARY_PATH çevre değişkeni kullanılmakta veya dinamik bağlayıcıya, --library-path seçeneği ile, arama listesi açık bir şekilde geçirilebilmektedir.

Örnek uygulamamızı rpath kullanmaksızın yeniden derleyip çalıştıralım.

gcc -odriver driver.c -Llib -ltest
$ ./driver
./driver: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory

Beklediğimiz üzere, dinamik bağlayıcı kütüphane dosyasın bulamamakta. Bu durumda LD_LIBRARY_PATH çevre değişkenini kullanabiliriz.

$ LD_LIBRARY_PATH=. ./driver
foo

Diğer bir alternatif ise dinamik bağlayıcı uygun şekilde direkt olarak çağırmak şeklinde olabilir.

$ /lib64/ld-linux-x86-64.so.2 --library-path . ./driver
foo

/etc/ld.so.conf ve /etc/ld.so.cache Dosyaları

Paylaşımlı kütüphaneler bir çok farklı dizinde bulunabilmektedir, tüm bu dizinlerin yükleme zamanında dinamik bağlayıcı tarafından aranması oldukça maliyetli olacaktır. Bu yüzden /etc/ld.so.conf ve /etc/ld.so.cache dosyası kullanılmaktadır.

/etc/ld.so.conf dosyasının içeriği aşağıdaki gibidir.

$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf

/etc/ld.so.conf.d/ dizininde ise sonu .conf ile biten, dizin yolu gösteren, dosyalar bulunmaktadır. Bu dizindeki dosyalardan birinin içeriği aşağıdaki gibidir.

cat x86_64-linux-gnu.conf
# Multiarch support
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu

/etc/ld.so.conf dosyası üzerinden bir çok .conf dosyasına ulaşılmaktadır. Kendimiz de buraya .conf uzantılı başka dosya isimleri ekleyebiliriz. ldconfig uygulaması ile tüm bu .conf dosyalarının içerdiği dizinler, ardından /lib ve /usr/lib dizinleri dolaşılarak /etc/ld.so.cache dosyası oluşturulmakta veya güncellenmektedir. /etc/ld.so.cache içinde kütüphane isimleri ve yol ifadeleri bulunmaktadır. Bu dosya bir yazı dosyası olmadığından içeriğine ldconfig -p şeklinde bakabiliriz. Sistemimizde, cache dosyasındaki ilk kayıtlar aşağıdaki gibidir.

$ ldconfig -p /etc/ld.so.cache | head
1520 libs found in cache `/etc/ld.so.cache'
        libzvbi.so.0 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzvbi.so.0
        libzvbi-chains.so.0 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzvbi-chains.so.0
        libzephyr.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzephyr.so.4
        libzeitgeist-2.0.so.0 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzeitgeist-2.0.so.0
        libzeitgeist-1.0.so.1 (libc6,x86-64) => /usr/lib/libzeitgeist-1.0.so.1
        libzbar.so.0 (libc6,x86-64) => /usr/lib/libzbar.so.0
        libz.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libz.so.1
        libz.so.1 (libc6) => /lib/i386-linux-gnu/libz.so.1
        libz.so (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libz.so

Dinamik bağlayıcı bu cache dosyasına bakarak aradığı kütüphanelere hızlı bir şekilde ulaşabilmektedir.

Kütüphanelerin Aranma Sırası

Dinamik bağlayıcı kütüphaneleri belli bir sıraya göre aramaktadır. Öncelik sıralaması aşağıdaki gibidir.

1. Bağımlılık listesinde "/" karakteri varsa kütüphane ismiyle beraber yol ifadesinin de geçirildiği kabul edilir ve arama bu dizinde yapılır. Yol ifadesi mutlak veya göreli olabilir.

2. Dosya DT_RUNPATH listesi içermiyorsa DT_RPATH alanındaki liste aranır. Her iki alanında arama listesi içermesi ilk bakışta anlamsız gelebilir fakat DT_RUNPATH öncesi bağlayıcıların da çalışabilmesi için statik bağlayıcılar DT_RPATH alanına da DT_RUNPATH bilgilerini kopyalabilmektedirler. Bu durumda eski bağlayıcılar yalnız DT_RPATH içeriğine bakarken, yeni bağlayıcılar DT_RUNPATH bir liste içeriyorsa bu aşamada ELF dosya içindeki arama dizinlerini gözardı eder.

3. LD_LIBRARY_PATH çevre değikenin değerine bakılır.

4. DT_RUNPATH listesine bakılır. DT_RUNPATH alanının eklenme nedeni, ELF içindeki arama dizinlerine LD_LIBRARY_PATH değişkeninden sonra bakılmak istenmesidir.

5. /etc/ld.so.cache dosyasına bakılır.

6. /lib ve /usr/lib dizinlerine bakılır.

results matching ""

    No results matching ""