Порядок завантаження динамічних бібліотек у Linux

Березень 19th, 2011

Бувають іноді такі ситуації, коли треба запустити програму так, щоб вона підхопила якусь специфічну версію динамічних бібліотек замість тих, що стандартно встановлені у системі.

Уявіть, наприклад, що ви тестер і маєте перевірити роботу програми з усіма можливими версіями Qt від 4.4.3 аж до 4.7 . Або вам просто хочеться у новенькій убунті вести розробку під якусь стародавню систему, у якій нічого більшого ніж 4.4.3 немає. У цьому пості я спробую зібрати докупи усю інформацію, яка знадобиться для вирішення таких ситуацій.

Коли бінарному файлу потрібно буде завантажити динамічну бібліотеку, то її шукатимуть у наступних місцях:

1) За значенням RPATH бібліотеки, у якій було знайдено залежність (якщо ця залежність у бібліотеці)
2) За значенням RPATH виконуваного файлу
3) у директоріях, перелічених у змінній оточення LD_LIBRARY_PATH
4) За значенням RUNPATH виконуваного файлу.
5) /etc/ld.so.cache — кеш бібліотек, сформований ldconfig
6) у стандартних директоріях, призначених для зберігання бібліотек (/lib та /usr/lib)

RPATH

RPATH — це список каталогів, розділений двокрапкою. Лінкер записує його у певне місце виконуваного файлу (або динамічної бібліотеки).

Дізнатися значення RPATH можна за допомогою команди readelf:

bear@bear-VB:~$ readelf -d /usr/bin/qtcreator | grep RPATH
0x0000000f (RPATH) Library rpath: [$ORIGIN/../lib/qtcreator]

Тут $ORIGIN — директорія, у якій лежить виконуваний файл.

Так же само можна подивитися значення RPATH для динамічних бібліотек

bear@bear-VB:~$ readelf -d /usr/lib/qtcreator/libExtensionSystem.so.1.0.0 | grep RPATH
0x0000000f (RPATH) Library rpath: [$ORIGIN/../lib/qtcreator]

Під час компіляції значення RPATH можна встановити за допомогою опції -rpath

bear@bear-VB:~/temp/dlp/cprog$ gcc cmain.c -o cprog -Wl,-rpath,/home/beardog/lib/
bear@bear-VB:~/temp/dlp/cprog$ readelf -d cprog | grep RPATH
0x0000000f (RPATH) Library rpath: [/home/beardog/lib/]

Крім того, в Debian та його нащадках є програма chrpath, призначена спеціально для того, щоб переглядати та змінювати RPATH. Типу

bear@bear-VB:~/temp/dlp/cprog$ chrpath -l ./cprog
./cprog: RPATH=/home/beardog/lib/
bear@bear-VB:~/temp/dlp/cprog$ chrpath -r /home/bd/lib ./cprog
./cprog: RPATH=/home/beardog/lib/
./cprog: new RPATH: /home/bd/lib
bear@bear-VB:~/temp/dlp/cprog$ chrpath -l ./cprog
./cprog: RPATH=/home/bd/lib

В принципі, використовувати RPATH у реальних системах не рекомендують, хоча для деяких не дуже складних ситуацій цей варіант може виявитися найкращим.

LD_LIBRARY_PATH

LD_LIBRARY_PATH — змінна оточення, що містить список директорій, розділених двокрапкою. У реальних системах її також не рекомендують (ще тут) використовувати, однак для розробки і тестування цей спосіб є найзручнішим. Очевидна перевага — можливість налаштувати запуск конкретної програми, не змінюючи при цьому саму програму і процес її створення.

Є можливість не встановлювати змінну оточення, а передавати безпосередньо завантажувачу як опцію командного рядка. Типу
bear@bear-VB:~$ /lib/ld-linux.so.2 --library-path /home/bearbog/lib /bin/echo ssss
ssss

RUNPATH

В принципі, RUNPATH — це ще один варіант RPATH, просто з міркувань безпеки його перенесли на крок назад.

Для читання можна використовувати той же readelf. Для встановлення під час створення програми треба передати лінкеру ті ж опції, що й для RPATH, і ще додати –enable-new-dtags. Особисто я так і не зрозумів, що робить ця опція; скоріше за все, буде одночасно встановлено і RPATH, і RUNPATH.

bear@bear-VB:~/temp/dlp/cprog$ gcc cmain.c -o cprog2 -Wl,-rpath,/home/bd2/lib/,--enable-new-dtags
bear@bear-VB:~/temp/dlp/cprog$ readelf -d cprog2 | grep -e "RPATH" -e "RUNPATH"
0x0000000f (RPATH) Library rpath: [/home/bd2/lib/]
0x0000001d (RUNPATH) Library runpath: [/home/bd2/lib/]

Кеш бібліотек.

Це якраз той спосіб, що зазвичай використовується у більшості дистрибутивів. Суть у тому, що є такий собі файл /etc/ld.so.cache; він містить інформацію про місцезнаходження усіх динамічних бібліотек, що знаходяться у системі.

Формується цей файл утилітою ldconfig. Теоретично, під час кожного запуску системи ldconfig має читати з текстового файлу /etc/ld.so.conf список директорій, у яких можуть знаходитись бібліотеки, переглядає їх і запам’ятовує, шо де лежить. Насправді все трохи складніше: /etc/ld.so.conf містить лише інструкцію про те, що шляхи до бібліотек потрібно взяти з текстових файлів, що знаходяться у /etc/ld.so.conf.d/. Ось як це виглядає в Убунті:

bear@bear-VB:~/temp$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
bear@bear-VB:~/temp$ ls /etc/ld.so.conf.d/
GL.conf i686-linux-gnu.conf libasound2.conf libc.conf
bear@bear-VB:~/temp$ cat /etc/ld.so.conf.d/i686-linux-gnu.conf
# Multiarch support
/lib/i686-linux-gnu
/usr/lib/i686-linux-gnu
bear@bear-VB:~/temp$ cat /etc/ld.so.conf.d/libasound2.conf
/usr/lib/alsa-lib

ldconfig -p виводить список усіх зареєстрованих бібліотек.

bear@bear-VB:~/temp/dlp/cprog$ ldconfig -p | grep QtXml.so
libQtXml.so.4 (libc6) => /usr/lib/libQtXml.so.4
libQtXml.so (libc6) => /usr/lib/libQtXml.so

Очевидно, що основним недоліком ldconfig є те, що налаштування відбувається одночасно для всієї системи. Взагалі це добре, але якщо треба запустити одночасно пару програм з різними версіями бібліотек, доводиться шукати інші шляхи.

Інші речі, про які треба знати

Існує ряд змінних оточення, що можуть суттєво вплинути на поведінку завантажувача. Частина
з них перелічена у man ld-linux, частина у man ld.so, частина ще десь . Наприклад, якщо задати змінну LD_RUN_PATH, то її значення буде використано замість RPATH виконуваного файлу (не перевіряв, можливо, це відбуватиметься лише у випадку, коли rpath для виконуваного файлу не задавався).

Змінна LD_PRELOAD (а також, можливо, файл /etc/ld.so.preload) може містити список бібліотек (файлів, а не директорій, як у попередніх випадках), які обов’язково мають бути завантажені до початку виконування бінарного файлу. Між іншим, саме таким чином рекомендують використовувати гугловий профайлер

LD_DEBUG може приймати значення “all”, “files” або “help”. Коли вона встановлена, /lib/ld-linux.so.2 починає видавати купу всякої інформації про те, які бібліотеки було завантажено для даного файлу.

Ще слід пам’ятати, що для set-user-ID/set-group-ID binaries (грубо кажучи, “програми, запущені з-під sudo”) більшість змінних оточення буде проігноровано.

Категорії: linux |

Залишити коментар