Рейтинг@Mail.ru
[Войти] [Зарегистрироваться]

Наши друзья и партнеры

UnixForum
Беспроводные выключатели nooLite

Lines Club

Ищем достойных соперников.

Книги по Linux (с отзывами читателей)

Библиотека сайта или "Мой Linux Documentation Project"

Ошибка базы данных:
На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Linux-инструменты для Windows-программистов
Назад Компиляция и сборка приложений Вперед

Библиотеки: связывание

О создании собственных библиотек мы поговорим в следующем разделе, а пока о том, как использовать уже существующие библиотеки со своими приложениями... Для использования библиотеки со своим приложением, объектный код приложения должен быть скомпонован с отдельными объектными модулями, извлекаемыми из библиотеки. Этой работой занимается компоновщик (линкер) из комплекта программ проекта GCC, вот как проделывается это в два шага, детализировано, двумя командами — на примере любого простейшего приложения (это могут быть показанные две последовательные команды в терминале, или две строки сценария Makefile): 3

$ gcc -c hello.c -o hello.o

$ ld /lib/crt0.o hello.o -lc -o hello 

Первая команда только компилирует (ключ -c) С-код приложения (hello.c) в объектный вид, а вторая команда вызывает компоновщик, имя которого ld, и который компонует полученный объектный файл с
  а). стартовым объектным модулем, указанным абсолютным именем /lib/crt0.o и
  б). со всеми необходимыми объектными модулями функций из стандартной библиотеки С — libc.so
(о том, как соотносятся имя библиотеки, указываемое в ключах сборки, и имя файла библиотеки — смотрите чуть ниже).

Но обычно компоновщик не вызывается явно, он вызывается неявно из gcc и с требуемыми умалчиваемыми параметрами. Вот форма, полностью эквивалентная предыдущей:

$ gcc -c hello.c -o hello.o

$ gcc hello.o -o hello

- здесь gcc, вызвав компоновщик ld, передаст ему и имя стартового объектного модуля (/lib/crt0.o) и имя стандартной библиотеки С (libc.so) как параметры по умолчанию. Эквивалентным (по результату) будет и вызов:

$ gcc hello.c -o hello

- свёрнутый в одну строку, в котором сначала вызывается компилятор, а затем компоновщик, эту форму мы уже неоднократно видели, но за ней скрывается весь механизм, который мы только-что развёрнуто рассмотрели.

В зависимости от того, какой формы библиотеки используются для сборки приложения, они могут (должны) компоноваться с приложением (способ компоновки определяется ключом -B):

- статически:

$ gcc -Bstatic -L<путь> -l<библиотека> ...

- динамически:

$ gcc [-Bdynamic] -L<путь> -l<библиотека> ...

- или смешано:

$ gcc -Bstatic -l<библиотека1> ... -Bdynamic -l<библиотека2> ...

Причём, в смешанной записи статические и динамические библиотеки могут чередоваться произвольное число раз:

$ gcc -Bstatic -l<библ1> <библ2> -Bdynamic -l<библ3> -l<библ4> -Bstatic -l<библ5> -l<библ6> ... \

-Bdynamic -l<библ7> ...

Если способ связывания не определён в командной строке (не указан ключ -B), то по умолчанию предполагается динамический способ связывания.

Теперь относительно имён библиотек и файлов... В качестве значения опции -l мы указываем имя библиотеки. Но сами библиотеки содержатся в файлах! Имена файлов, содержащих статические библиотеки, имеет расширение .a (archive), а файлов, содержащих динамические библиотеки - .so (shared objects). А само имя файла библиотеки образуется конкатенацией префикса lib и имени библиотеки. Таким образом, в итоге, если мы хотим скомпоновать свою программу prog.c с разделяемой библиотекой с именем xxx, то мы предполагаем наличие (на путях поиска) библиотечного файла с именем libxxx.so, и записываем команду компиляции так:

$ gcc prog.c -lxxx -oprog  

А если мы хотим проделать то же, но со статической библиотекой, то мы должны иметь библиотечный файл с именем libxxx.a, и записываем команду компиляции так:

$ gcc prog.c -Bstatic -lxxx -oprog  

Посмотреть на какие файлы разделяемых библиотек ссылается уже собранная бинарная (формата ELF) программа можно командой:

$ ldd hello

        linux-gate.so.1 =>  (0x00f1b000)
        libc.so.6 => /lib/libc.so.6 (0x00b56000)
        /lib/ld-linux.so.2 (0x00b33000)

Здесь могут ожидать неожиданности: могут быть указаны те же имена библиотек, но не с тем полным путевым именем, которое мы имели в виду - это может быть не та версия библиотеки, обуславливающая не то поведение приложения.

Некоторую сложность могут вызывать только оставшиеся вопросы: а). где компоновщик ищет библиотеки при компоновке, и б). где и как исполняемая программ ищет библиотеки для загрузки... и как это всё грамотно определить при сборке. Пути умалчиваемого поиска библиотек при компоновке находим в описании:

$ man ld

LD(1)                       GNU Development Tools                       LD(1)
NAME
       ld - The GNU linker
SYNOPSIS
...

Это обычно /lib и /usr/lib.

Последовательность (в порядке приоритетов) поиска библиотек компоновщиком:

  1. По путям, указанным ключом -L
  2. По путям, указанным списком в переменной окружения LD_LIBRARY_PATH
  3. По стандартным путям: /lib и /usr/lib
  4. По путям, которые сохранены в кэше загрузчика (/etc/ld.so.cache)

Для статической компоновки процесс поиска на этом и заканчивается. Смотреть текущее содержимое кэша загрузчика (мы здесь достаточно произвольно выделяем символьные строки из, вообще-то говоря, несимвольной последовательности байт, но это работает):

$ strings  '/etc/ld.so.cache' | head -n8

ld.so-1.7.0
glibc-ld.so.cache1.1
libzrtpcpp-1.4.so.0
/usr/lib/libzrtpcpp-1.4.so.0
libzltext.so.0.13
/usr/lib/libzltext.so.0.13
libzlcore.so.0.13
/usr/lib/libzlcore.so.0.13
...

Как попадают пути в кэш загрузчика? Посредством утилиты обновления (добавления) ldconfig из файла /etc/ld.so.conf ... но в новых системах этот файл фактически пустой:

$ cat /etc/ld.so.conf

include ld.so.conf.d/*.conf

- и включает в себя последовательное содержимое всех файлов каталога /etc/ld.so.conf.d. Поэтому, информация для обновления кэша накапливается из файлов этого каталога:

$ ls /etc/ld.so.conf.d

mysql-i386.conf  qt4-i386.conf  qt-i386.conf  usr-local-lib.conf  xulrunner-32.conf

Где, для примера, содержимое одного из выбранных наугад конфигурационных файлов и включает новый путь поиска библиотек:

$ cat /etc/ld.so.conf.d/usr-local-lib.conf

/usr/local/lib

Таким образом и каталог /usr/local/lib попадает в пути загрузки. Обычно ldconfig запускается последним шагом инсталляции программных пакетов (особенно из исходников), но не лишне бывает выполнить его и вручную, а детали вызова смотрим справкой:

$ ldconfig --help

Использование: ldconfig [КЛЮЧ...]
Конфигурирует связи времени выполнения
для динамического компоновщика.
...

Это всё касалось поиска любых (статических и динамических) библиотек на этапе компоновки. Но для динамических библиотек нужен ещё поиск периода старта приложения, использующего библиотеку (библиотека могла быть, например, перенесена со времени сборки приложения). Такой поиск производится функциями (динамического линкера), содержащимися в динамической библиотеке /lib/ld-2.13.so, с которой компонуется по умолчанию (без нашего вмешательства) любая программа, использующая разделяемые библиотеки. Требуемые приложению библиотеки ищутся на путях, указанных в ключах -rpath и -rpath-link при сборке программы, а затем по значению путей в списке переменной окружения с именем LD_RUN_PATH. Далее проводится поиск по п.п. 3,4 показанных ранее.

Существует два различных метода использования динамической библиотеки из приложения (оба метода берут свое начало в Sun Solaris). Первый способ – это динамическая компоновка вашего приложения с совместно используемой библиотекой. Это более традиционный способ который мы уже неявно начали рассматривать. При этом способе загрузку библиотеки при запуске приложения берёт на себя операционная система. Второй называют динамической загрузкой. В этом случае программа явно загружает нужную библиотеку, а затем вызывает определенную библиотечную функцию (или набор функций). На этом втором методе обычно основан механизм загрузки подключаемых программных модулей – плагинов. Этот способ может быть иногда особенно интересен разработчикам встраиваемого оборудования. Компоновщик не получает никакой информации о библиотеках и обьектах в ходе компоновки. Библиотека libdl.so (компонуемая к приложению) предоставляет интерфейс к динамическому загрузчику. Этот интерфейс составляют 4 функции: dlopen(), dlsym(), dlclose() и dlerror(). Первая принимает на вход строку с указанием имени библиотеки для загрузки, загружает библиотеку в память (если она еще не была загружена) или увеличивает количество ссылок на библиотеку (если она уже найдена в памяти). Возвращает дескриптор, который потом используется в функциях dlsym() и dlclose(). Функция dlclose(), соответственно, уменьшает счетчик ссылок на библиотеку и выгружает ее, если счетчик становится равным 0. Функция dlsym() по имени функции возвращает указатель на ее код. Функция dlopen() в качестве второго параметра получает управляющий флаг, определяющий детали загрузки библиотеки. Он может иметь следующие значения:

RTLD_LAZY - разрешение адресов по именам объектов происходит только для используемых объектов (это не относится к глобальным переменным — они разрешаются немедленно, в момент загрузки).

RTLD_NOW - разрешение всех адресов происходит до возврата из функции.

RTLD_GLOBAL - разрешает использовать объекты, определенные в загружаемой библиотеке для разрешения адресов из других загружаемых библиотек.

RTLD_LOCAL - запрещает использовать объекты из загружаемой библиотеки для разрешения адресов из других загружаемых библиотек.

RTLD_NOLOAD - указывает не загружать библиотеку в память, а только проверить ее наличие в памяти. При использовании совместно с флагом RTLD_GLOBAL позволяет «глобализовать» библиотеку, предварительно загруженную локально (RTLD_LOCAL).

RTLD_DEEPBIND - помещает таблицу загружаемых объектов в самый верх таблицы глобальных символов. Это гарантирует использование только своих функций и нивелирует их переопределение функций другими библиотеками.

RTLD_NODELETE - отключает выгрузку библиотеки при уменьшении счетчика ссылок на нее до 0.

Изложенной информации более чем достаточно, чтобы скомпоновать наше собственное приложение с любой, уже имеющейся в нашем распоряжении, библиотекой объектных модулей.


3. Конкретный пути и имена стартового объектного файла, и указание стандартной библиотеки в командной строке - могут заметно разниться от версии к версии и дистрибутива системы, показанная командная строка взята из CentOS 5.2 (ядро 2.6.18).


Предыдущий раздел: Оглавление Следующий раздел:
Библиотеки: использование   Библиотеки: построение


Средняя оценка 5 при 1 голосовавших
Вы сможете оценить статью и оставить комментарий, если войдете или зарегистрируетесь.
Только зарегистрированные пользователи могут оценивать и комментировать статьи.

Комментарии