В примерах, продемонстрированных ранее, имя, которое встраивалось в исполняемый файл и затем искалось динамическим компоновщиком на этапе выполнения, являлось также именем самой разделяемой библиотеки. Это имя называют
Если у разделяемой библиотеки есть имя soname, то во время статической компоновки оно встраивается в исполняемый файл вместо реального имени и, как следствие, используется динамическим компоновщиком для поиска библиотеки на этапе выполнения. Имя soname — это уровень абстракции, позволяющий выполняющейся программе задействовать версию библиотеки, отличную от той, с которой она была скомпонована (но совместимую с ней).
В разделе 41.6 будет показано, какими правилами принято руководствоваться при создании имени разделяемой библиотеки (реального и soname). Теперь же рассмотрим упрощенный пример, чтобы усвоить основные принципы.
Изначально soname указывается при создании разделяемой библиотеки:
$ gcc — g — c — fPIC — Wall mod1.c mod2.c mod3.c
$ gcc — g — shared — Wl, — soname,libbar.so — o libfoo.so mod1.o mod2.o mod3.o
Параметр — Wl, — soname,libbar.so заставляет компоновщик пометить разделяемую библиотеку libfoo.so именем soname libbar.so.
Чтобы определить soname существующей разделяемой библиотеки, можно воспользоваться любой из следующих команд:
$ objdump — p libfoo.so | grep SONAME
SONAME libbar.so
$ readelf — d libfoo.so | grep SONAME
0x0000000e (SONAME) Library soname: [libbar.so]
Теперь, когда у нас есть разделяемая библиотека с именем soname, можно создать исполняемый файл с помощью уже знакомой команды:
$ gcc — g — Wall — o prog prog.c libfoo.so
Но на этот раз компоновщик обнаруживает, что библиотека libfoo.so содержит имя soname libbar.so и встраивает его в исполняемый файл.
Теперь, попытавшись запустить программу, мы увидим следующее:
$ LD_LIBRARY_PATH=. /prog
prog: error in loading shared libraries: libbar.so: cannot open shared object file: No such file or directory
Проблема заключается вот в чем: динамический компоновщик не может найти ничего, что имело бы имя libbar.so. Необходимо выполнить еще один шаг: создать символьную ссылку, связывающую soname с реальным именем библиотеки. Эта ссылка должна находиться в одном из тех каталогов, по которым выполняет поиск динамический компоновщик. Следовательно, можно запустить программу следующим образом:
$ ln — s libfoo.so libbar.so
$ LD_LIBRARY_PATH=. /prog
Called mod1-x1
Called mod2-x2
На рис. 41.1 показаны этапы компиляции и компоновки, используемые в ходе создания разделяемой библиотеки с именем soname, а также процедура компоновки программы с этой библиотекой и создание символьной ссылки на имя soname, необходимой для запуска программы.
Рис. 41.1.
На рис. 41.2 показано, через какие этапы проходит вышеприведенная программа при загрузке в память и подготовке к выполнению.
Чтобы выяснить, какую разделяемую библиотеку использует процесс, можно просмотреть содержимое /proc/PID/maps (поддерживается только в Linux; см. раздел 48.5).
Рис. 41.2.
В этом разделе мы кратко рассмотрим несколько инструментов, которые могут пригодиться при анализе разделяемых библиотек, исполняемых файлов и скомпилированных объектов (файлов. o).
Команда ldd(1) (от англ. list dynamic dependencies — «вывести динамические зависимости») выводит разделяемые библиотеки, необходимые для работы программы (или другой разделяемой библиотеки). Например:
$ ldd prog
libdemo.so.1 => /usr/lib/libdemo.so.1 (0x40019000)
libc.so.6 => /lib/tls/libc.so.6 (0x4017b000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Команда ldd находит все модули, на которые ссылается библиотека (по тому же принципу, что и динамический компоновщик), и выводит результат в следующем виде:
Для большинства исполняемых файлов в формате ELF команда ldd выведет как минимум ld-linux.so.2 (динамический компоновщик) и libc.so.6 (стандартную библиотеку языка C).