Где linux ищет библиотеки
Как уже неоднократно упоминалось в предыдущей главе, библиотека — это набор скомпонованных особым образом объектных файлов. Библиотеки подключаются к основной программе во время линковки. По способу компоновки библиотеки подразделяют на архивы (статические библиотеки, static libraries) и совместно используемые (динамические библиотеки, shared libraries). В Linux, кроме того, есть механизмы динамической подгрузки библиотек. Суть динамической подгрузки состоит в том, что запущенная программа может по собственному усмотрению подключить к себе какую-либо библиотеку. Благодаря этой возможности создаются программы с подключаемыми плагинами, такие как XMMS. В этой главе мы не будем рассматривать динамическую подгрузку, а остановимся на классическом использовании статических и динамических библиотек.
С точки зрения модели КИС, библиотека — это сервер. Библиотеки несут в себе одну важную мысль: возможность использовать одни и те же механизмы в разных программах. В Linux библиотеки используются повсеместно, поскольку это очень удобный способ «не изобретать велосипеды». Даже ядро Linux в каком-то смысле представляет собой библиотеку механизмов, называемых системными вызовами.
Статическая библиотека — это просто архив объектных файлов, который подключается к программе во время линковки. Эффект такой же, как если бы вы подключали каждый из файлов отдельно.
В отличие от статических библиотек, код совместно используемых (динамических) библиотек не включается в бинарник. Вместо этого в бинарник включается только ссылка на библиотеку.
Рассмотрим преимущества и недостатки статических и совместно используемых библиотек. Статические библиотеки делают программу более автономной: программа, скомпонованная со статической библиотекой может запускаться на любом компьютере, не требуя наличия этой библиотеки (она уже «внутри» бинарника). Программа, скомпонованная с динамической библиотекой, требует наличия этой библиотеки на том компьютере, где она запускается, поскольку в бинарнике не код, а ссылка на код библиотеки. Не смотря на такую зависимость, динамические библиотеки обладают двумя существенными преимуществами. Во-первых, бинарник, скомпонованный с совместно используемой библиотекой меньше размером, чем такой же бинарник, с подключенной к нему статической библиотекой (статически скомпонованный бинарник). Во-вторых, любая модернизация динамической библиотеки, отражается на всех программах, использующих ее. Таким образом, если некоторую библиотеку foo используют 10 программ, то исправление какой-нибудь ошибки в foo или любое другое улучшение библиотеки автоматически улучшает все программы, которые используют эту библиотеку. Именно поэтому динамические библиотеки называют совместно используемыми. Чтобы применить изменения, внесенные в статическую библиотеку, нужно пересобрать все 10 программ.
В Linux статические библиотеки обычно имеют расширение .a (Archive), а совместно используемые библиотеки имеют расширение .so (Shared Object). Хранятся библиотеки, как правило, в каталогах /lib и /usr/lib. В случае иного расположения (относится только к совместно используемым библиотекам), приходится немного «подшаманить», чтобы программа запустилась.
3.2. Пример статической библиотеки
Теперь давайте создадим свою собственную библиотеку, располагающую двумя функциями: h_world() и g_world(), которые выводят на экран «Hello World» и «Goodbye World» соответственно. Начнем со статической библиотеки.
Начнем с интерфейса. Создадим файл world.h:
/* world.h */ void h_world (void); void g_world (void);
Здесь просто объявлены функции, которые будут использоваться.
Теперь надо реализовать серверы. Создадим файл h_world.c:
/* h_world.c */ #include #include "world.h" void h_world (void)
Теперь создадим файл g_world.c, содержащий реализацию функции g_world():
/* g_world.c */ #include #include "world.h" void g_world (void)
Можно было бы с таким же успехом уместить обе функции в одном файле (hello.c, например), однако для наглядности мы разнесли код на два файла.
Теперь создадим файл main.c. Это клиент, который будет пользоваться услугами сервера:
/* main.c */ #include "world.h" int main (void)
Теперь напишем сценарий для make. Для этого создаем Makefile:
# Makefile for World project binary: main.o libworld.a gcc -o binary main.o -L. -lworld main.o: main.c gcc -c main.c libworld.a: h_world.o g_world.o ar cr libworld.a h_world.o g_world.o h_world.o: h_world.c gcc -c h_world.c g_world.o: g_world.c gcc -c g_world.c clean: rm -f *.o *.a binary
Не забывайте ставить табуляции перед каждым правилом в целевых связках.
$ make gcc -c main.c gcc -c h_world.c gcc -c g_world.c ar cr libworld.a h_world.o g_world.o gcc -o binary main.o -L. -lworld $
Осталось только проверить, работает ли программа и разобраться, что же мы такое сделали:
$ ./binary Hello World Goodbye World $
Итак, в приведенном примере появились три новые вещи: опции -l и -L компилятора, а также команда ar. Начнем с последней. Как вы уже догадались, команда ar создает статическую библиотеку (архив). В нашем случае два объектных файла объединяются в один файл libworld.a. В Linux практически все библиотеки имеют префикс lib.
Как уже говорилось, компилятор gcc сам вызывает линковщик, когда это нужно. Опция -l, переданная компилятору, обрабатывается и посылается линковщику для того, чтобы тот подключил к бинарнику библиотеку. Как вы уже заметили, у имени библиотеки «обрублены» префикс и суффикс. Это делается для того, чтобы создать «видимое безразличие» между статическими и динамическими библиотеками. Но об этом речь пойдет в других главах книги. Сейчас важно знать лишь то, что и библиотека libfoo.so и библиотека libfoo.a подключаются к проекту опцией -lfoo. В нашем случае libworld.a «урезалось» до -lworld.
Опция -L указывает линковщику, где ему искать библиотеку. В случае, если библиотека располагается в каталоге /lib или /usr/lib, то вопрос отпадает сам собой и опция -L не требуется. В нашем случае библиотека находится в репозитории (в текущем каталоге). По умолчанию линковщик не просматривает текущий каталог в поиске библиотеки, поэтому опция -L. (точка означает текущий каталог) необходима.
3.3. Пример совместно используемой библиотеки
Для того, чтобы создать и использовать динамическую (совместно используемую) библиотеку, достаточно переделать в нашем проекте Makefile.
# Makefile for World project binary: main.o libworld.so gcc -o binary main.o -L. -lworld -Wl,-rpath,. main.o: main.c gcc -c main.c libworld.so: h_world.o g_world.o gcc -shared -o libworld.so h_world.o g_world.o h_world.o: h_world.c gcc -c -fPIC h_world.c g_world.o: g_world.c gcc -c -fPIC g_world.c clean: rm -f *.o *.so binary
Внешне ничего не изменилось: программа компилируется, запускается и выполняет те же самые действия, что и в предыдущем случае. Изменилась внутренняя суть, которая играет для программиста первоочередную роль. Рассмотрим все по порядку.
Правило для сборки binary теперь содержит пугающую опцию -Wl,-rpath,. Ничего страшного тут нет. Как уже неоднократно говорилось, компилятор gcc сам вызывает линковщик ld, когда это надо и передает ему нужные параметры сборки, избавляя нас от ненужной платформенно-зависимой волокиты. Но иногда мы все-таки должны вмешаться в этот процесс и передать линковщику «свою» опцию. Для этого используется опция компилятора -Wl,option,optargs. Расшифровываю: передать линковщику (-Wl) опцию option с аргументами optargs. В нашем случае мы передаем линковщику опцию -rpath с аргументом . (точка, текущий каталог). Возникает вопрос: что означает опция -rpath? Как уже говорилось, линковщик ищет библиотеки в определенных местах; обычно это каталоги /lib и /usr/lib, иногда /usr/local/lib. Опция -rpath просто добавляет к этому списку еще один каталог. В нашем случае это текущий каталог. Без указания опции -rpath, линковщик «молча» соберет программу, но при запуске нас будет ждать сюрприз: программа не запустится из-за отсутствия библиотеки. Попробуйте убрать опцию -Wl,-rpath,. из Makefile и пересоберите проект. При попытке запуска программа binary завершится с кодом возврата 127 (о кодах возврата будет рассказано в последующих главах). То же самое произойдет, если вызвать программу из другого каталога. Верните обратно -Wl,-rpath. пересоберите проект, поднимитесь на уровень выше командой cd .. и попробуйте запустить бинарник командой world/binary. Ничего не получится, поскольку в новом текущем каталоге библиотеки нет.
Есть один способ не передавать линковщику дополнительных опций при помощи -Wl — это использование переменной окружения LD_LIBRARY_PATH. В последующих главах мы будем подробно касаться темы окружения (environment). Сейчас лишь скажу, что у каждого пользователя есть так называемое окружение (environment) представляющее собой набор пар ПЕРЕМЕННАЯ=ЗНАЧЕНИЕ, используемых программами. Чтобы посмотреть окружение, достаточно набрать команду env. Чтобы добавить в окружение переменную, достаточно набрать export ПЕРЕМЕННАЯ=ЗНАЧЕНИЕ, а чтобы удалить переменную из окружения, надо набрать export -n ПЕРЕМЕННАЯ. Будьте внимательны: export — это внутреннаяя команда оболочки BASH; в других оболочках (csh, ksh, . ) используются другие команды для работы с окружением. Переменная окружения LD_LIBRARY_PATH содержит список дополнительных «мест», разделенных двоеточиеями, где линковщих должен искать библиотеку.
Не смотря на наличие двух механизмов передачи информации о нестандартном расположении библиотек, лучше помещать библиотеки в конечных проектах в /lib и в /usr/lib. Допускается расположение библиотек в подкаталоги /usr/lib и в /usr/local/lib (с указанем -Wl,-rpath). Но заставлять конечного пользователя устанавливать LD_LIBRARY_PATH почти всегда является плохим стилем программирования.
Следующая немаловажная деталь — это процесс создания самой библиотеки. Статические библиотеки создаются при помощи архиватора ar, а совместно используемые — при помощи gcc с опцией -shared. В данном случае gcc опять же вызывает линковщик, но не для сборки бинарника, а для создания динамической библиотеки.
Последнее отличие — опциии -fPIC (-fpic) при компиляции h_world.c и g_world.c. Эта опция сообщает компилятору, что объектные файлы, полученные в результате компиляции должны содержать позиционно-независимый код (PIC — Position Independent Code), который используется в динамических библиотеках. В таком коде используются не фиксированные позиции (адреса), а плавающие, благодаря чему код из библиотеки имеет возможность подключаться к программе в момент запуска.
Использование библиотек в Ubuntu
У меня есть исходный код либы, я его собрал в двух вариантах(релиз и дебаг), после сборки двух вариантах генерятся сами либы и вспомогательный софт(графический софт чтобы проверить устройства подключенные) соответственно версии релиз и дебаг тоже . После сборки библиотеки (cmake->make -jn -> make install) у меня в папках релиз и дебаг появились 3 файла: bin, lib, include. В директории bin папки релиз лежат файлы с такими же именами что и в директории bin в папке дебаг, у них только размеры разные(из за разных режимов сборок). У меня вопрос, почему дебажные либы не помечаются как в винде буквой «d» как в виндовс? После сборки я добавил директории lib из папок релиз и дебаг в LD_LIBRARY_PATH. Когда начал проверять какие либы тянет софт( использовал lld), который лежит в папке бин, оказалось, что софт из директории bin папки релиз использует библиотеки, которые расположены в дебаге.
Как мне заставить использовать софт из директории bin папки релиз библиотеки, которые находятся в директории lib той жей папки релиз? (Либу которую я собираю intel realsense, JC: ubuntu 20.04, g++ 9.3) И как мне вообще заставить софт которые собран под дебаг использовать дебажные либы, а не релизные. Я хочу сделать так, как было в Windows на Visual Studio: когда пишешь код, то дебажная версия использует только либы версии дебаг, а релизная — релиз, соответственно при отладке использовалась дебажная версия бибилиотеки. Я новичок в использовании linux, недавно начал активно пользоваться.
Отслеживать
задан 27 мая 2021 в 9:46
71 4 4 бронзовых знака
в линукс не используются виндовые соглашения о именовании файлов. Я бы строго не рекомендовал добавлять одновременно и релиз и дебаг каталоги в пути поиска. Скорее всего Ваш софт умеет работать с правильными библиотеками, просто попробуйте запустить в чистом окружении.
27 мая 2021 в 9:53
@KoVadim Но как раз я и проверил с помощью команды lld и увидел что релизная версия тянет дебажную либу. Что значит запустить в чистом окружении?
27 мая 2021 в 10:07
например, в виртуалке
27 мая 2021 в 10:42
@KoVadim получается что бы запустить нужную версию софта с нужной версией либы, необходимо в пафе указывать только путь к нужной версией либы?
27 мая 2021 в 11:08
крайне желательно. Но вообще то в линуксе принятно хранить либы в правильных каталогах /usr/lib, /usr/local/libs и тому подобное. Это обычно и делает install — копирует либы туда. Понятно, что в момент разработки это не очень хорошая идея и поэтому используют LD_PRELOAD и LD_LIBRARY_PATH
Управление библиотеками в Linux
![]()
В данной статье мы посмотрим, что такое статические и динамические библиотеки. Местоположение библиотек по умолчанию. Определение используемых библиотек. Загрузка библиотек.

Библиотеки это набор функций, которые могут использоваться в различных программах. Библиотеки могут быть статические (библиотека привязывается к определенной программе или софт содержит данную библиотеку в своем теле.) и динамическими (библиотеки грузятся в оперативную память и используются). Плюсы первого варианта нет проблемы совместимости, т. к. софт уже в себе содержит библиотеку, библиотека всегда с собой. Но при этом программы становятся большие по размеры и т.к каждая может загружать свои библиотеки, а иногда и одинаковые. Второй вариант значительно лучше, сами программы по своему размеру меньше. Библиотека загружается один раз в оперативку. И следующая программа, которой необходимы такие же функции, берет и использует эти данные.
По умолчанию библиотеки в Linux находятся в двух папках. Это корневая папка /lib в ней находятся библиотеки, которые используют программы, расположенные в корневой папке /bin.
И есть вторая папка /usr/lib. В ней находятся библиотеки, которые используют программы расположенные /usr/bin. Пути к библиотекам указаны файле /etc/ld.so.conf. Данный файл можно просмотреть стандартным способом, через утилиту cat.
Видим, что написано включить все библиотеки, которые расположены по пути, указанном в файле. Те которые оканчиваются на .conf. Он просто включает в себя все настройки, которые находятся в конфигурационных файлах, в данной директории. Переходим в данную директорию.
В данной директории мы можем видеть 2 файла конфигурации, в зависимости от версии и наполнения операционной системы их может быть и больше. Ну и соответственно в конфигурационных файлах находятся пути к директориям, где лежат необходимые для работы библиотеки. Если мы ставим какое, то свое программное обеспечение, которому необходимы дополнительные библиотеки, не идущие в составе дистрибутива linux, то в данной директории может создаться свой конфигурационный файл. Например: если мы используем систему виртуализации VMware, то к каждой VM устанавливаем VMware tools то данное программное обеспечение создаст свой конфигурационный файл с путями для своих библиотек.
Переходим в директорию cd /etc/ и отсортируем так, чтобы в результатах все, что содержит ld.
ls | grep ld.
Видим 3 основных конфигурационных файла. ld.so.conf — это файл конфигурации в котором написано откуда брать дополнительные библиотеки. Директория ls.so.conf.d в которой находятся дополнительные конфигурационные файлы и ld.so.cache это кэш библиотек. Он у нас выстраивается каждый раз для того, чтобы программы при необходимости при запросе библиотек не копались в файлах, а сразу брали из загруженного в оперативную память кэша. Т.е. если мы вносим какие-то изменения в файл конфигурации, добавляем какие-то конфигурационные файлы нам необходимо обновить этот кэш. Кэш обновляется командой ldconfig. Этого, собственно, достаточно, чтобы прогрузить все библиотеки в кэш.
Давайте посмотрим, как, определить какими библиотеками пользуется какая программа.
Для этого мы будем использовать команду ldd и путь к бинарному файлу. Например: Программа ls которая используется для вывода списка файлов в каталоге. Она находится в каталоге /bin/ls.
В результате получим мы следующее:
Мы видим, какие so использует данная программа и соответственно ссылки на них, где они расположены, собственно, so — это наши библиотеки в данном случае.
Возможно добавление библиотек вручную, это может потребоваться если мы ставим совершенно стороннее программное обеспечение, которое очень трудно взаимодействует с Linux или устаревшее. Т.е. которое само не может создать конфигурационный файл и разнести библиотеки в системные директории Linux. Если мы хотим сделать это вручную, тогда нам необходим тот самый файл /etc/ld.so.conf. В данный файл мы можем дописать путь к файлу конфигурации библиотек тех, которые нам нужны. Либо есть более легкий вариант с использованием переменной export LD_LIBRARY_PATH и указать путь к тем особенным библиотекам, которые будет использовать наша «особенная» программа. Обычно все стороннее программное обеспечение устанавливается в папку /opt. Итоговый вариант будет выглядеть как: export LD_LIBRARY_PATH=/opt/soft/lib и когда пройдет экспорт, у нас попробует погрузится из этого пути библиотека, но перед этим необходимо не забыть сделать ldconfig.
Где linux ищет библиотеки
Существует набор базовых действий, которые практически любая программа выполняет одинаково – открытие файла, чтение и запись данных и тому подобное. Разделяемые библиотеки предназначены для того, чтобы предоставить прикладным программам готовые интерфейсы функций для выполнения каких-либо более-менее стандартных действий. Разделяемая библиотека, как понятно из названия, может использоваться множеством программ. В настоящий момент стандартным форматом для разделяемых библиотек в Linux является ELF (Executable Linked Format).
Каждый файл ELF имеет заголовок, в котором описывается, какие секции содержит этот файл. Секции объединяют однотипные данные, и их детальное описание можно прочитать в справочном руководстве ( man elf ). Мы же выделим следующую информацию: каждая библиотека содержит список имен переменных и функций, которые она содержит и предоставляет другим (экспортирует) и список переменных и функций, которые необходимо взять в других библиотеках, а также секции инициализации и деинициализации. Экспортируемые и импортируемые объекты (переменные и функции) называют символами библиотеки.
Большинство исполняемых файлов программ также имеют формат ELF, и на самом деле отличаются от библиотек в основном тем, что не имеют экспортируемых функций. Загрузчик ELF (он же dl, dynamic linker и dynamic loader) умеет загружать в память код ELF-файла, анализировать его структуру для определения списков экспортируемых и импортируемых символов и загружать необходимые для работы программы библиотеки.
Когда пользователь пытается запустить какую–либо программу, первым начинает работу загрузчик ELF. Он загружает в память процесса бинарный файл и выделяет, какие символы и из каких библиотек необходимо догрузить в память. После дозагрузки каждой библиотеки загрузчик связывает символы (проставляет реальные адреса) из загруженной библиотеки и повторяет цикл анализа на предмет того, какую библиотеку нужно загрузить. Когда все нужные библиотеки загружены, загрузчик передает управление коду инициализации каждой из загруженных библиотек в порядке, обратном загрузке, после чего передает управление коду программы. По завершении программы загрузчик снова “проходится” по всем библиотекам и вызывает их функции деинициализации. Если на этапе загрузки какой – либо библиотеке возникает ошибка, загрузчик сообщит об этом пользователю. Наиболее типичные ошибки dl – это не найденный файл библиотеки или неразрешимый символ (символ не был найден в библиотеке, в которой ожидался).
Вполне естественно, что загрузчик ищет библиотеки не по всей файловой системе, а только в определенных каталогах. Это каталоги /lib , /usr/lib и те, которые были перечислены системным администратором в файле /etc/ld.so.conf . Уточним, что этот файл на самом деле используется только системной утилитой ldconfig , сам же загрузчик использует кэш-файл /etc/ld.so.cache . Обновить этот кэш-файл можно путем простого запуска ldconfig без параметров. Следствием этого является то, что если вы установили в систему новые библиотеки, не мешает вызвать ldconfig .
В некоторых дистрибутивах есть возможность включать в ld.so.conf дополнительные файлы без его изменения. Для этого в ld.so.conf включается специальная строка вида:
include ld.so.conf.d/*.conf
Это приводит к тому, что каталоги, перечисленные в файлах с расширением conf , расположенных в каталоге /etc/ld.so.conf.d будут использованы для поиска разделяемых библиотек:
$cat /etc/ld.so.confinclude ld.so.conf.d/*.conf /usr/lib/mysql /usr/X11R6/lib /usr/lib/qt-3.3/lib$ls /etc/ld.so.conf.d/oracle$cat /etc/ld.so.conf.d/oracle/opt/oracle/9i/lib
Нередко возникает ситуация, когда пользователю необходимо запустить какую-либо программу, которая не находится в каталогах, описанных в /etc/ld.so.conf . В таких ситуациях можно воспользоваться специальным “люком”, оставленным разработчиками dl специально для таких случаев: дело в том, что кроме загрузки библиотек с использованием данных из ld.so.cache загрузчик проверяет факт наличия библиотеки с указанным именем в каталогах, перечисленных в переменной среды LD_LIBRARY_PATH .
Разработчики часто используют еще одну возможность ld: если файл некоторой разделяемой библиотеки указан в переменной LD_PRELOAD , эта библиотека принудительно загружается и ее символы считаются более “приоритетными” и перекрывают одноименные символы, если таковые существуют в других библиотеках, загружаемых ld при запуске на выполнение бинарного файла ELF.
Попробуем рассмотреть примеры использования указанных возможностей dl: пусть есть некоторый программный продукт, в состав которого кроме собственно исполняемых программ входят разделяемые библиотеки (например, таковы практически все продукты, разработанные с помощью Borland Kylix). Если мы установим такой пакет, например, в /opt/program , его исполняемые файлы в /opt/program/bin а разделяемые библиотеки в /opt/program/lib , то программа, скорее всего, не будет запускаться, поскольку не сможет загрузить необходимых библиотек. Для того, чтобы программы пакета начали запускаться, мы должны “объяснить” ld где именно искать библиотеки. Рассмотрим возможные способы, которыми мы можем воздействовать на ld чтобы добиться нужного нам результата.
Первый способ – указать каталог с библиотеками перед запуском программы и уже затем запустить программу (ld воспользуется значением переменной для того, чтобы попытаться найти библиотеки по указанному пути):
$export LD_LIBRARY_PATH=/opt/program/lib$/opt/program/bin/filename
Второй способ – добавить каталог /opt/program/lib в файл /etc/ld.so.conf и запустить ldconfig , решив проблему с невозможностью нахождения этих библиотек для всех программ сразу:
$su -#echo /opt/program/lib >>/etc/ld.so.conf#ldconfig#exit$/opr/program/bin/filename
Можно также воспользоваться возможностью принудительной загрузки тех библиотек, которые необходимы программе для запуска:
$export LD_PRELOAD=/opt/program/lib/*$ /opr/program/bin/filename
Большая часть кода разделяемых библиотек находится в кэше и становится доступна процессам через отображение файла в память. Это отображение делается с правами доступа “только чтение”, что защищает код библиотек от переписывания его неправильно работающими или просто злонамеренными программами.
| В начало → Linux не для идиотов → LD, Shared Library, SO и много страшных слов |