Перейти к содержимому

Shared library что это

  • автор:

Параметр при сборки библиотеки «shared lib» — что это значит?

Подскажите пожалуйста, что значит собрать библиотеку с параметром «shared lib» ? То есть «разделяемая библиотека» — что и с кем она разделяет ? Это значит, что библиотека будет динамически подключаемая ? Типа .dll для windows ?

Отслеживать
задан 26 ноя 2021 в 10:07
1,045 7 7 серебряных знаков 16 16 бронзовых знаков
да, это будет dll или so (для линукса) или dlyn (вроде так) для мака
26 ноя 2021 в 10:28

Это библиотека разделяется между процессами. т.е. исполняемый код будет присутствовать в оперативной памяти в одном экземпляре.

26 ноя 2021 в 10:34

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

TL;DR

Динамическая библиотека — скомпилированный бинарь, который сам по себе не является программой, но предоставляет какие-то функции, которые могут импортировать другие программы во время своей работы. Пример — sprintf() из libc в *NIX и ntdll.dll в Windows.

Про этапы компиляции

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

Трансляция

В это время транслятор (часть компилятора, которая отвечает за перевод человекочитемого исходника в бинарный вид) парсит даннный на вход файл, проверяет базовый синтаксис и пытается оттранслировать его в бинарный вид. На выходе мы получаем *.o или *.obj файл, в котором содержится бинарный вид компилируемого исходника. В этот момент мы еще не можем его запустить — в таком виде любой прыжок не в программе не определен. А прыжков, даже в линейной программе, наподобие «Hello world!», хватает — мы так или иначе подключаем библиотеки, чтобы самим с нуля не писать функции для вывода на экран (да и, спойлер, без дергания ядра ОС это все равно невозможно, а чтобы работать с ядром все равно нужны библиотеки).

Для примера разберем этап трансляции того же Hello world’a: Если мы хотм, чтобы компилятор не зашел дальше трансляции, то у GCC существует замечательный ключ -c :

#include int main(void)
$ gcc -c main.o main.c 

И посмотрим содержимое обьектного файла с помощью objdump :

 main.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 : 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # b b: 48 89 c7 mov %rax,%rdi e: e8 00 00 00 00 call 13 13: b8 00 00 00 00 mov $0x0,%eax 18: 5d pop %rbp 19: c3 ret 

Как видно, здесь просто наша функция main, которая оттранслирована в асм. Причем call идет на 00 00 00 00 — то есть по нулевому смещению, о чем нам objdump сообщает, показывая, что прыжок будет на , то есть следующую строчку. По понятным причинам, такой файл не является исполняемым.

Линковка

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

Продолжим мучить Hello world. На прошлом этапе мы получили обьектный файл, в котором не посчитаны адреса. Что ж, теперь посчитаем адреса и получим исполняемый файл:

$ gcc -o main main.o 

(Я не буду прикладывать весь вывод objdump ‘a, потому что он реально огромен, а просто приложу еще раз функцию main). Остальные функции являются служебными и служат для запуска программы/использования функций из динамических библиотек

0000000000001139 : 1139: 55 push %rbp 113a: 48 89 e5 mov %rsp,%rbp 113d: 48 8d 05 c0 0e 00 00 lea 0xec0(%rip),%rax # 2004 1144: 48 89 c7 mov %rax,%rdi 1147: e8 e4 fe ff ff call 1030 114c: b8 00 00 00 00 mov $0x0,%eax 1151: 5d pop %rbp 1152: c3 ret 1153: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1) 115a: 00 00 00 115d: 0f 1f 00 nopl (%rax) 

Как мы видим, здесь у call ‘a появился адрес, теперь мы прыгаем не «вникуда», а куда-то, где определена функция puts (на самом деле, это тоже не совсем так, но об этом опять-таки ниже).

Немного про библиотеки в целом

Для начала немного о терминологии. Очень много раз видел, как хедеры ( *.h файлы) называют библиотеками. Это не совсем корректно. По-хорошему говоря, в хедере (если он написан правильно) нет определений функций — только их обьявления. А вот сами функции уже лежат в библиотеке, хедер нужен исключительно для того, чтобы корректно вызывать функции в программе.

Статические библиотеки

Первый тип библиотек — статические. Это означает, что во время линковки библиотека жестко оказывается вшита внутрь бинарника. По сути, статическая библиотека является архивом с обьектными файлами, которые содержат реализации функций из хедера. То есть, когда мы создаем статическую библиотеку, то вместо вызова линковщика после трансляции мы собираем все хедеры в один архив и предоставляем это конечному пользователю как библиотеку.

Рассмотрим пример (опять будут *NIX): У нас есть два хедера с соответствующими *.c файлами и main.c , который эти хедеры использует. Соберем из всего этого добра статическую библиотеку и попытаемся получить полноценную программу:

#ifndef BYE_H #define BYE_H void print_bye(void); #endif 
#include "bye.h" #include void print_bye()
#ifndef PRINT_H #define PRINT_H void print_hello(void); #endif 
#include "hello.h" #include void print_hello(void)
#include "hello.h" #include "bye.h" int main(void)

Собираем хедеры, как мы уже умеем:

$ gcc -c hello.c bye.c 

Получаем два обьектных файла. Теперь нужно их собрать в архив с помощью ar . Тут небольшое отступление про нейминг — есть договоренность, что имена библиотек должны начинаться с lib . И линковщик при указании бибилотеки, как мы увидим далее, пытается найти файл начинающийся именно с lib . Поэтому создадим библиотеку print , с названием файла libprint.a :

$ ar rc libprint.a hello.o bye.o 

Следующий этап — добавление индекса символов к библиотеке, чтобы линковщик смог понять, какие функции/переменные/етц есть, а каких нет. Вообще, в большинстве случаев ar добавляет такой индекс сам, но есть ситуации, когда он этого не делает. Поэтому хорошим тоном считается добавлять его руками, даже если это ничего не изменит:

$ ranlib libprint.a 

После чего мы получили статическую библиотеку, которая при линковке окажется жестко зашита в бинарник. Посмотрим, что это означает. Скомпилируем с этой библиотекой и откроем через objdump (обратите внимание, что название библиотеки мы указываем без префикса lib , линковщик сам его добавит:

$ gcc main.c -L. -lprint -o main $ objdump -d main 
0000000000001139 : 1139: 55 push rbp 113a: 48 89 e5 mov rbp,rsp 113d: e8 22 00 00 00 call 1164 1142: e8 07 00 00 00 call 114e 1147: b8 00 00 00 00 mov eax,0x0 114c: 5d pop rbp 114d: c3 ret 000000000000114e : 114e: 55 push rbp 114f: 48 89 e5 mov rbp,rsp 1152: 48 8d 05 ab 0e 00 00 lea rax,[rip+0xeab] # 2004 1159: 48 89 c7 mov rdi,rax 115c: e8 cf fe ff ff call 1030 1161: 90 nop 1162: 5d pop rbp 1163: c3 ret 0000000000001164 : 1164: 55 push rbp 1165: 48 89 e5 mov rbp,rsp 1168: 48 8d 05 a0 0e 00 00 lea rax,[rip+0xea0] # 200f 116f: 48 89 c7 mov rdi,rax 1172: e8 b9 fe ff ff call 1030 1177: 90 nop 1178: 5d pop rbp 1179: c3 ret 117a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0] 

Как мы можем видеть, наши функции оказались в бинарнике. Это и есть статическая библиотека — все функции оказываются внутри. Плюсы такого подхода — невозможно использовать подмены библиотек, один файл, что проще распространять и так далее. Минусы — огромный вес файла-результата. Поэтому существует второй подход.

Динамические библиотеки

Далее, что же такое динамические (они же разделяемые, хотя этот перевод очень кривой и, его, по-хорошему, стоит избегать) библиотеки. Глобальное их отличие от статических — они не оказываются внутри бинарника, а лежат где-то в системе, но при запуске программы оказываются в ее памяти благодаря механизму отображения файлов (подробнее про это можно почитать вот тут).

Лирическое отступление. Динамические библиотеки называются иногда разделяемыми как раз благодаря вот этому механизму. Потмоу что, по сути, файл физически на диске один. Отсюда и название в Linux/UNIX Shared Object ( *.so ). Но по-русски проще и понятнее для окружающих говорить «динамические библиотеки». Тем более, что в Windows и OS X они называются Dynamic Linked Library ( *.dll ) и Dynamic Library ( *.dylib ) соответственно.

Пересоберем написанный выше код в динамическую библиотеку и так же попытаемся получить исполняемый файл. В этот раз, нам придется собирать обьектные файлы в виде Position Independent Code — у нас ведь цель, чтобы код работал одинаково в разных участках памяти. Поэтому теперь сборка обьектного файла будет выглядеть как-то так:

$ gcc -c -fPIC bye.c hello.c 

После этого мы получаем два обьектных файла, которые могут быть собраны в *.so . Самое время приступить:

$ gcc -shared -o libprint.so bye.o hello.o 

На выходе мы получаем динамическую библиотеку libprint.so , которая, на самом деле, тоже использует динамическую библиотеку! Ведь функция puts() , на самом деле, предоставляется библиотекой libc.so . Соберем бинарник:

$ gcc main.c -L. -lprint -o main 
./main 
./main: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory 

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

LD_LIBRARY_PATH=. ./main 

В этот раз все хорошо, библиотека подгрузилась и мы увидели две заветные строчки вывода нашей программы. Если мы посмотрим на бинарник в objdump , то увидим, что функции print_hello и print_bye находтся в секции plt , которая описывает, как вызвать функции из динамических библиотек. Если вам интересна эта тема и как работают таблицы PLT , GOT и вообще динамическая загрузка — советую погуглить на эту тему, материала много и разобраться достаточно легко.

UPD. Чуть не забыл — существует второй способ работы с динамическими библиотеками, чтобы каждый раз не указывать LD_LIBRARY_PATH . Можно жестко вшить путь библиотеки в сам бинарь, но это чревато последствиями. Делается это с помощью rpath :

$ gcc -L -l -rpath= -o

UPD2. В некоторых ситуациях возникает необходимость загрузить свою библиотеку вместо системной — к примеру, своя реализация функции rand() . Это делается с помощью LD_PRELOAD . Возьмем, для примера, следующий код:

#ifndef RAND_H #define RAND_H int rand(void); #endif 
#include "rand.h" int rand(void)
#include #include #include int main() < srand (time(NULL)); for(int i = 0; i < 5; i++) < printf ("%d\n", rand() % 100); >return 0; > 

Если собрать отдельно динамическую библиотеку и отдельно бинарь, то все будет как обычно — даже если указать библиотеку в LD_LIBRARY_PATH :

$ ./main 74 59 1 47 11 
$ LD_LIBRARY_PATH=. ./main 88 21 88 79 9 

Но стоит только определить LD_PRELOAD как происходит магия и мы получаем ответ на главный вопрос вселенной:

$ LD_PRELOAD=./ld_rand.so ./main 42 42 42 42 42 

Если хотите немного про это почитать, то вот неплохая точка входа

Создание разделяемых библиотек

В наши дни в условиях постоянного развития процесс создания программ является плодом эволюции навыков и опыта, выработанного программистами и разработчиками.


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

После создания файлов с исходным кодом программы, они дожны быть странслированы в блоки кода, которые может исполнять машина. Обычно об этом коде говорят как об объектном коде . Этот код выполняет те же самые действия, что и исходный код, за исключением того, что он написан на особом языке, который может непосредственно выполняться машиной. Процесс трансляции исходного кода в объектный код известен как компиляция . Компиляция выполняется по частям и за один раз компилируется (в зависимости от компилятора) часть программы и обычно один или несколько файлов. Скомпилированный объектный код содержит программу, подпрограмму, переменные и т.д. — части программ, которые были странслированы и которые готовы к следующему шагу.

После того, как были созданы все файлы программы с машинным кодом, их необходимо соединить или скомпоновать при помощи операции, выполняемой специальной утилитой, называемой компоновщик . На этой стадии все ссылки, которые код модуля делает на код, принадлежащий другому модулю, «разрешаются» (такие, как вызов подпрограммы или ссылки на переменные, принадлежащие или определеные в другом модуле). Результирующим продуктом является программа, которую можно непосредственно загружать и исполнять.

2.- Краткая история процесса создания программы.

Процесс создания программы постоянное развивается, это необходимо для достижения наиболее эффективного исполнения программ или лучшего использования системных ресурсов.

В начале программы писались непосредственно в машинном коде. Позднее стало ясно, что можно писать программы на языке более высокого уровня, так как последующую трансляцию в машинный код можно автоматизировать благодаря систематической природе трансляции. Это увеличило производительность программ.

После того, как научились компилировать программы (я упростил эволюцию компиляции, на самом деле это был очень трудный шаг, поскольку это очень сложный процесс), процесс создания программ стал состоять из создания файла с исходным кодом программы, его компиляции и в конце концов исполнения.

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

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

Тогда стало возможным разрабатывать программы, которые поддерживают эти функции без затраты дополнительных усилий по созданию их кода еще и еще раз. Даже тогда, процесс был сложен из-за того, что при компоновке программ было необходимо объединять все части и они должны были быть известны программисту (это внесло дополнительные затраты на проверку возможности использования известной функции, которая использует/требует другие неизвестные функции).

3.- Что такое библиотека?

Описанная выше проблема привела к созданию библиотек. Это ничто иное, как особый вид файла (если быть точным, то архив, tar(1) или cpio(1) ) с особыми параметрами, формат которых компоновщик понимает, и когда мы указываем ему библиотечный архив, КОМПОНОВЩИК ВЫБИРАЕТ ТОЛЬКО ТЕ МОДУЛИ, КОТОРЫЕ НУЖНЫ ПРОГРАММЕ и исключает все остальное. Появилось новое преимущество. Теперь можно было разрабатывать программы, которые используют большие библиотеки функций, и программисту вовсе не обязательно знать все зависимости функций в этой библиотеке.

Те библиотеки, которые мы пока обсудили, не пошли в развитии дальше. К ним только добавился файл, часто находящийся в начале архива, содержащий описания модулей и идентификаторы, которые компоновщик должен разрешать без чтения всей библиотеки (и тем самым устраняя необходимость в чтении библиотеки по нескольку раз). Этот процесс (добавление таблицы символов в архив библиотеки) в Линуксе выполняется командой ranlib(1). Описанные пока библиотеки известны как СТАТИЧЕСКИЕ БИБЛИОТЕКИ.

Прогресс произошел после появления первой многозадачной системы: разделение кода . Если в одной и той же системе были запущены две копии одного и того же кода, было бы желательно, чтобы оба процесса могли совместно использовать один код, так как обычно программа не изменяет свой собственный код. Эта идея устраняет необходимость в размещении в памяти нескольких копий, что освобождает большое количество памяти в огромных многопользовательских системах.

Развивая это последнее нововведение на один шаг дальше, кто-то (я не знаю, кто это был, но идея была отличная 😉 подумал, что очень часто многие программы используют одну и ту же библиотеку, но, будучи разными программами, части используемой библиотеки вовсе не обязательно были теми же частями, используемыми другой программой. Более того, основной код был другой (они же разные программы), поэтому их тексты не были разделяемыми. Что же, наш герой подумал, что если разные программы, использующие одну и ту же библиотеку, могли делить между собой эту библиотеку, то мы бы смогли выгадать немного памяти. Теперь разные программы, имея разный текст, работают с одним и тем же кодом библиотеки.

Однако теперь процесс стал сложнее. Исполняемая программа не полностью скомпонована, но разрешение ссылок на идентификаторы библиотек откладываются до момента загрузки программы. Компоновщик (в случае Линукса это ld(1)) распознает вызовы разделяемых библиотек и не включает их код в программу. Сама система, ядро, при исполнении exec() распознает запуск программы, использующей разделяемые библиотеки и исполняет специальный код, загружающий разделяемые библиотеки (выделяя разделяемую память для их текста, выделяя закрытую память для значений библиотеки и т.д.). Теперь этот процесс выполняется при загрузке исполняемого файла и вся процедура сильно усложнилась.

Конечно же, когда компоновщик встречается с обычной библиотекой, он продолжает вести себя также, как и раньше.

Разделяемая библиотека не является архивом, содержащим объектный код, скорее это файл, содержащий сам объектный код. Во время компоновки программы с разделяемой библиотекой, компоновщик не исследует библиотеку, какие модули надо добавлять в программу, а какие нет. Он только удостоверяется, что неразрешенные ссылки становятся разрешенными, и определяет, что необходимо добавить в список при включении библиотеки. Можно сделать архивную ar(1) библиотеку всех разделяемых библиотек, но часто это не делается, так как разделяемые библиотеки часто являются результатом компоновки различных модулей, поэтому библиотека понадобится позднее, во время исполнения. Возможно, название разделяемая библиотека не является самым уместным и было бы точнее назвать ее разделяемым объектом (тем не менее, поскольку нас могут не понять, мы не используем этот термин).

4.- Виды библиотек.

Как мы уже говорили, в Линукс существует два вида библиотек: статические и разделяемые. Статические библиотеки являются набором модулей, объединенных в архив при помощи утилиты ar(1) и проиндексированных утилитой ranlib(1). Эти модули часто хранятся в файле с окончанием .a (я не использую термин расширение потому, что в Линуксе концепция расширения файла не используется). Компоновщик распознает окончание файла .a и начинает искать модули, как если бы это была статическая библиотека, выбирает и добавляет в программу те модули, которые разрешают неразрешенные ссылки.

В отличие от статических, разделяемые библиотеки являются не архивами, а перемещаемыми объектами, обозначенными особым кодом (который обозначает их как разделяемые библиотеки). Компоновщик ld(1), как уже говорилось, не добавляет модули в программный код, а выбирает идентификаторы, предоставляемые библиотекой как разрешенные, добавляет те, которые необходимы самой библиотеке, и продолжает работу, не добавив никакого кода, считая, что требуемый код уже был добавлен в основной код. Компоновщик ld(1) распознает разделяемые библиотеки по окончанию .so (не .so.xxx.yyy, мы обсудим этот вопрос позднее).

5.- Процесс компоновки в Линукс.

Каждая программа состоит из объектных модулей, скомпонованных в исполняемый файл. Эту операцию выполняет используемый в Линуксе компоновщик ld(1).

ld(1) поддерживает различные опции, которые изменяют его поведение, но мы ограничимся здесь только теми, которые связаны с использованием библиотек в общем. ld(1) вызывается не непосредственно пользователем, а самим компилятором gcc(1) на его завершающей стадии. Небольшие познания о его modus operandis помогут нам понять способ использования библиотек в Линукс.

Для правильной работы ld(1) требуется список объектов, которые необходимо скомпоновать с программой. Эти объекты можно задавать в любом порядке (*) пока мы выполняем указанное выше соглашение, как уже говорилось, разделяемая библиотека распознается по окончанию .so (а не .so.xx.yy) и статическая библиотека по .a (и конечно же, простыми объектными файлами являются те, чье имя заканчивается на .o).

(*) Это не совсем верно. ld(1) включает только те модули, которые разрешают ссылки на момент включения библиотеки, поэтому в модуле, влкюченном позднее, все равно может быть ссылка, которая позднее, поскольку это не проявляется на момент включения этой библиотеки, может вызвать команду на влкючение необходимых библиотек.

С другой стороны, ld(1) позволяет включать стандартные библиотекии благодаря опциям -l и -L.

Но. Что мы понимаем под стандартной библиотекой, в чем разница? Никакой. Только то, что ld(1) ищет стандартные библиотеки в определенных местах, тогда как те, что описаны в списке параметров как объекты, ищутся по именам их файлов.

По умолчаиню библиотеки ищутся в каталогах /lib и /usr/lib (хотя я слышал, что в зависимости от версии/реализации ld(1) , могут быть дополнительные каталоги). -L позволяет нам добавить каталоги к тем, которые просматриваются при обычном поиске библиотек. Она используется заданием -L каталог для каждого каталога , который мы хотим добавить. Стандартные библиотеки указываются опцией -l Имя (где Имя указывает библиотеку, которую необходимо загрузить) и ld(1) будет искать, в соответствующем порядке, в соответствующих каталогах файл с именем libИмя.so. Если он не будет найден, то будет сделана попытка найти libИмя.a , его статическую версию.

Если ld(1) находит файл libИмя.so , он скомпонует ее как разделяемую библиотеку, тогда как если он найдет файл libИмя.a, он скомпонует модули, полученные из нее, если они разрешают любые неразрешенные ссылки.

6.- Динамическая компоновка и загрузка разделяемых библиотек

Динамическая компоновка выполняется в момент загрузки исполняемого файла особым модулем (на самом деле этот особый модуль является самой разделяемой библиотекой), называемым /lib/ld-linux.so .

На самом деле существуют два модуля для компоновки динамических библиотек: /lib/ld.so (для библиотек, использующих старый формат a.out) и /lib/ld-linux.so (для библиотек, использующих новый формат ELF).

Особенность этих модулей заключается в том, что они должнны загружаться каждый раз, когда происходит динамическая компоновка программ. Их имена стандартны (причина в том, что их нельзя перемещать из каталога /lib , а также нельзя изменять их имена). Если мы заменим имя /etc/ld-linux.so , то мы автоматически остановим использование любой программы, использующей разделяемые библиотеки, поскольку этот модуль отвечает за разрешение всех ссылок, не разрешенных во время исполнения.

Последнему модулю помогает существование файла /etc/ld.so.cache , в котором для каждой библиотеки указываются наиболее подходящий исполняемый файл, содержащий эту библиотеку. Мы вернемся к этой теме позднее.

7.- soname. Версии исполняемых библиотек. Совместимость.

Мы подошли к наиболее запутанной теме, связанной с разделяемыми библиотеками: их версии.

Часто встречается сообщение ‘library libX11.so.3 not found,’ оставляющее нас в растеряности: обладая библиотекой libX11.so.6 мы неспособны ничего сделать. Как стало возможным, что ld.so(8) признает взаимозаменяемыми библиотеки libpepe.so.45.0.1 и libpepe.so.45.22.3 и не признает libpepe.so.46.22.3?

В Линукс (и во всех операционных системах, использующих формат ELF) библиотеки идентифицируются отличающей их последовательностью символов: soname.

soname включается в саму библиотеку и эта последовательность определяется при компоновке объектов, формирующих библиотеку. При создании разделяемой библиотеки, чтобы дать значение этой символьной строке необходимо передать ld(1) опцию ( -soname ).

Эта последовательность символов используется динамическим загрузчиком для идентификации разделяемой библиотеки, которую необходимо загрузить, и идентификации исполняемого файла. Это выглядит примерно так:
Ld-linux.so определяет, что программа требует библиотеку и определяет ее soname. Затем идет поиск имени в /etc/ld.so.cache и определяется имя файла, содержащего эту библиотеку. Далее запрошенное soname сравнивается с именем существующей библиотеки, и если они идентичны, то значит она нам и нужна! Если нет, то поиск будет продолжаться до тех пор, пока она не будет найдена, или, если она не будет найдена, будет выдано сообщение об ошибке.

По soname можно определить, подходит ли библиотека для загрузки, потому что ld-linux.so проверяет, совпадает ли требуемое soname с требуемым файлом. В случае различия мы можем получить знаменитое ‘libXXX.so.Y not found’ . Ищется именно soname и выдаваемая ошибка определяется soname.

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

soname библиотекии, по умолчанию, должно идентифицировать соответствующую библиотеку и ИНТЕРФЕЙС этой библиотеки. Если мы внесем изменения в библиотеку, которые затрагивают только внутреннюю функциональность, но интерфейс останется неизменным (количество функциий, переменных, параметры функций), то две библиотеки будут взаимозаменяемыми и в целом мы скажем, что изменения были незначительными (обе библиотеки совместимы и мы можем заменить одну на другую). Если это происходит, то часто изменяется минорный номер (который не входит в состав soname) и библиотека может быть заменена без значительных проблем.

Однако, когда мы добавляем функции, убираем функции и в целом ИЗМЕНЯЕМ ИНТЕРФЕЙС библиотеки, то уже невозможно утверждать, что эта библиотека взаимозаменяема с предыдущей (например замена libX11.so.3 на libX11.so.6 является частью перехода с X11R5 на X11R6, при этом вводятся новые функциии и поэтому изменяется интерфейс). Переход с X11R6-v3.1.2 на X11R6-v3.1.3 вероятно не вызовет изменений в интерфейсе и у библиотеки останется то же soname — хотя, чтобы сохранить старую версию, нам потребуется дать ей другое имя (по этой причине номер версии завершает имя библиотеки, тогда как в soname задаются только мажорные номера).

8.- ldconfig(8)

Как мы уже говорили раньше, /etc/ld.so.cache позволяет ld-linux.so конвертировать soname файла, содержащегося в библиотеке. Это бинарный (для большей эффективности) файл, созданный утилитой ldconfig(8) .
ldconfig(8) создает для каждой динамической библиотеки, найденной в каталогах, указанных в /etc/ld.so.conf , символическую ссылку с именем библиотеки soname. В этом случае, когда ld.so хочет получить имя файла, то что он делает, это выбирает в списке каталогов файл с требуемым soname. И поэтому нет необходимости каждый раз запускать ldconfig(8) при добавлении библиотеки. Мы запускаем ldconfig только когда мы добавляем каталог к списку.

9.- Я хочу сделать динамическую библиотеку.


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

Разделяемая библиотека загружается в память полностью (а не только необходимые модули), поэтому, чтобы быть полезной, она должна использоваться полностью. Наихудшим примером использования динамической библиотеки является использование только одной функции, а 90% библиотеки вряд ли когда используется.

Хорошим примером динамической библиотеки является стандартная библиотека C (она используется всеми программами, написанными на C). В среднем используются все функции.

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

9.1.- Компиляция исходных кодов

Компиляция исходных кодов выполняется точно также, как и в случае с обычным исходным кодом, за исключением того, что для создания кода, который можно загружать в различных позициях в пространстве виртуальных адресов процесса, мы будем использовать опцию ‘-f PIC’ (позиционно-независимый код).

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

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

9.2.- Компоновка объектов в библиотеку

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

gcc -shared -o libИмя.so.xxx.yyy.zzz -Wl,-soname,libИмя.so.xxx


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

-o libИмя.so.xxx.yyy.zzz .
Это имя выходного файла. Вовсе не обязательно следовать соглашению по имени, но если мы хотим, чтобы эта библиотека стала стандартом для будущих разработок, то лучше им следовать.

-Wl,-soname,libИмя.so.xxx .
Опция -Wl говорит gcc(1) , что далее идут опции (разделенные запятыми), которые предназначены для компоновщика. Этот механизм используется gcc(1) для передачи опций ld(1) . В примере мы передаем компоновщику следующие опции:

-soname libИмя.so.xxx
9.3.- Установка библиотеки

Что ж, у нас уже есть соответствующий исполняемый файл. Теперь, чтобы его можно было использовать, его необходимо установить в соответствующее место.

Для компиляции программы, которая требует нашу новую библиотеку, необходимо использовать следующую команду:

gcc -o program libИмя.so.xxx.yyy.zzz

или, если библиотека была установлена в каталог (/usr/lib) , будет достаточно:

gcc -o программа -lИмя


    Скопируйте библиотеку в каталог /lib или /usr/lib . Если вы решите скопировать ее в другое место (например в /usr/local/lib) , то вы не сможете быть уверенными, что при компоновке программ компоновщик ld(1) найдет ее автоматически.

Выполните ldconfig(1) для создания символьной ссылки libИмя.so.xxx.yyy.zzz на libИмя.so.xxx. На этом шаге нам станет известно, правильно ли мы выполнили все предыдущие шаги и распознается ли библиотека как динамическая. На этом шаге оказывается влияние только на загрузку библиотеки во время исполнения, а не на компоновку программ.

10.- Создание статической библиотеки

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

Замечание: Компоновщик при поиске библиотек сначала ищет файл с именем libИмя.so , и лишь затем libИмя.a. Если мы назовем обе этих библиотеки (статическую и динамическую версии) одним и тем же именем, в общем-то будет невозможно определить, какая из двух будет скомпонована в каждом случае (динамическая всегда компонуется первой, поскольку компоновщик обнаруживает ее первой).

По этой причине, если необходимо иметь две версии одной библиотеки, всегда рекомендуется называть статическую в виде libИмя_s.a , а динамическую libИмя.so . Тогда при компоновке нужно будет указать:

gcc -o program -lИмя_s

для компоновки со статической версией, тогда как для динамической:

gcc -o program -lИмя
10.1.- Компиляция исходного кода

Для компиляции исходного кода вовсе не обязательно принимать каких-либо особых мер. Точно также положение объектов решается на стадии компоновки, не обязательно компилировать с опцией -f PIC (хотя возможно продолжать ее использовать).

10.2.- Компоновка объектов в библиотеку

В случае статических библиотек компоновка не выполняется. Все объекты архивируются в библиотечный файл командой ar(1) . Далее, чтобы быстро разрешить символы желательно выполнить команду ranlib(1) над библиотекой. Хотя это и не обязательно, невыполнение этой команды может разкомпоновать модули в исполняемом файле потому, что при обработке модуля компоновщиком во время создания библиотеки не все непрямые зависимости между модулями разрешаются немедленно: скажем, модулю, находящемуся в конце архива, требуется другой модуль, находящийся в начале архива, это означает, что для разрешения всех ссылок требуется несколько проходов по одной и той же библиотеке.

10.3.- Установка библиотеки

Статические библиотеки желательно называть в формате libName.a только в том случае, если есть желание иметь только статические библиотеки. В случае двух видов библиотек я бы порекомендовал называть их libИмя_s.a , с тем, чтобы было легче различать, когда загружать статическую, а когда динамическую библиотеку.

Процесс компоновки позволяет ввести опцию -static. Эта опция управляет загрузкой модуля /lib/ld-linux.so , и не влияет на порядок поиска библиотек, поэтому если кто-то укажет -static и ld(1) найдет динамическую библиотеку, то он будет работать с ней (а не продолжать искать статическую библиотеку). Это приведет к ошибкам во время исполнения из-за вызовов процедур в библиотеке, которая не входит в состав исполняемого файла — модуль для автоматической динамической загрузки не скомпонован и поэтому процесс не может быть выполнен.

11.- Сравнение статической и динамической компоновки

Предположим, что мы хотим создать дистрибутив программы, которая использует библиотеку, которую мы можем распространять только включенную в программу статически, и ни в какой иной форме (примером этого случая являются приложения, созданные с использованием Motif).

Создать такую программу можно двумя способами. Первый заключается в создании статически скомпонованного исполняемого файла (используя только библиотеки .a и не используя динамического загрузчика). Этот вид программ загружается один раз и не требуют ни одной библиотеки из системы (даже /lib/ld-linux.so) . Однако у них есть недостаток — все необходимое нужно держать в одном бинарном файле и поэтому это обычно очень большие файлы. Вторым вариантом является создание динамически скомпонованной программы, то есть в среде, в котором наше приложение будет выполняться, должны быть все соответсвующие динамические библиотеки. Исполняемый файл может быть очень маленьким, хотя иногда невозможно иметь абсолютно все библиотеки (например есть люди, у которых нет Motif).

Существует третий вариант, смешанный, в котором некоторые библиотеки скомпонованны динамически, а остальные статически. В этом случае было бы логично выбрать конфликтующую библиотеку в ее статической форме и все остальные в их динамической форме. Этот вариант является очень распространенной формой дистрибутива программ.

Например, можно скомпилировать три различные версии программы следующим образом:

gcc -static -o program.static program.o -lm_s -lXm_s -lXt_s -lX11_s\ -lXmu_s -lXpm_s gcc -o program.dynamic program.o -lm -lXm -lXt -lX11 -lXmu -lXpm gcc -o program.mixed program.o -lm -lXm_s -lXt -lX11 -lXmu -lXpm

В третьем случае только библиотека Motif (-lXm_s) компонуется статически, а все остальные компонуются динамически. Та среда, в которой будет запускаться программа, должны обладать соответствующими версиями библиотек libm.so.xx libXt.so.xx libX11.so.xx libXmu.so.xx и libXpm.so.xx

Перевод на русский: Владимир Попов
© 1998 Luis Colorado
This website is mantained by Miguel A Sepulveda .

Shared library что это

Иван Осокин

Оцените автора

Добавить комментарий Отменить ответ

Свежие записи

  • Холодильник Димрота для самогонного аппарата: что это такое, как выбрать?
  • RCS-сообщения: что это такое, как отключить?
  • Креп: что это за еда, рецепты и фото
  • Египет – самые удивительные достопримечательности и места для посещения
  • Пришло письмо из соцзащиты: что это означает?

Вам также может понравиться

Фирменный магазин Apple – это официальный интернет-ресурс

Современная технологическая эра непрерывно приводит

С развитием технологий сгенерировать текст с помощью

В современном мире вопрос утилизации и переработки

В современном мире драйвером потребления товаров и

Современные технологии активно вступают в каждый аспект

Туроператор Тез Тур – это одна из самых популярных

WhatsApp – одно из самых популярных мессенджеров в

  • Политика конфиденциальности
  • Пользовательское соглашение

​Временно возглавляющий минпром Башкирии Фарит Гильманов (ранее

Вы заметили как с момента прихода Динара Халилова началась

Shared library что это

Динамическая библиотека компилируется в код, который может быть загружен и использоваться позднее при запуске исполняемого файла.

Когда компилятор создает исполняемый файл, исходные файлы сначала преобразуются в объектные файлы. Затем объектные файлы линкуются, для создания исполняемого файла. Динамическая библиотека так же, как статическая библиотека содержит файлы объектов. Но динамическая библиотека также как исполняемый файл загружается при запуске исполняемого файла.

Библиотека является динамической (или общей) потому, что код библиотеки загружается из исполняемого файла во время выполнения, более того, библиотека может быть загружена несколькими исполняемыми файлами, несмотря на то, что используется одна копия динамической библиотеки.

Когда библиотека создана, мы можем использовать ее код, как если бы мы скомпилировали его в нашу программу.

Пример динамической библиотеки

Ниже приводится простой пример создания динамической (общей) библиотеки с помощью этих трех файлов:

  • mylib.bas — исходный файл для библиотеки
  • mylib.bi — заголовок для библиотеки
  • mytest.bas — тестовая программа

Наша библиотека будет иметь один модуль, с одной функцией:

» mylib.bas
» компиляция: fbc -dll mylib.bas

» Сложение д вух чисел и возвращение результата
Public Function Add2 ( ByVal x As Integer , ByVal y As Integer ) As Integer Export
Return ( x + y )
End Function

Компиляция библиотеки:
fbc -dll mylib.bas

Опция -dll говорит компилятору о том, что надо взять исходный код mylib.bas, и превратить его в объектный файл mylib.o, а затем сохранить файл объекта в файл динамической библиотеки. Имя библиотеки будет содержать расширение .so или .dll в зависимости от платформы, на которой библиотека собирается ( Linux или Windows соответственно). Библиотека может содержать множество модулей (исходных файлов), каждый модуль может иметь множество функций, но для этого простого примера, мы использовали один модуль.

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

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

» mylib.bi
#inclib «mylib»
Declare Function Add2 ( ByVal x As Integer , ByVal y As Integer ) As Integer

Нет необходимости писать это в заголовке. Мы можем использовать данную запись и в исходном коде нашей программы. Заголовок же поможет избежать лишнего кода, если данную библиотеку нужно будет включить и для других исходных файлов. Заявление #inclib сообщит компилятору имя динамической библиотеки, которую надо будет вызвать из нашего исполняемого файла во время выполнения программы.

С нашей библиотекой (.dll/.so файлом) и заголовком (.bi файлом) мы можем попробовать протестировать нашу программу:

» mytest.bas
» компиляция: fbc mytest.bas
#include once «mylib.bi»
Print Add2 ( 1 , 2 )

Оператор #include сообщает компилятору включить исходный код mylib.bi так, как если бы мы ввели его содержимое в исходном источнике. То есть все что мы в нем написали (запись о библиотеке) будет передана компилятору.

Компиляция:
fbc mytest.bas

Затем, когда мы запускаем наш исполняемый файл mytest, мы должны получить результат:
3

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

Динамические библиотеки могут по желанию содержать отладочную информацию, указанную с опцией -g командной строки.

Объектные файлы, и библиотеки, являются платформозависимыми, а в некоторых случаях конкретизируются для определенной версии компилятора и FreeBASIC RinTime .

Использование динамической библиотеки в Windows

В Windows динамические библиотеки должны находится либо в глобальных каталогах, либо рядом с исполняемым файлом, который ее использует .

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

  • Каталог, из которого был загружен исполняемый файл.
  • Текущий каталог.
  • Папка Windows и системная папка Windows.
  • Каталоги, указанные с помощью PATH в переменных средах.

Порядок, в котором производится поиск каталогов может зависеть от используемой версии Windows и ее настроек.

Использование динамической библиотеки в Linux

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

  • Скопировать .so файл в каталог с динамическими библиотеками (т.е. /usr/lib) и запустить ldconfig для настройки библиотеки.
  • Изменить переменную окружения LD_LIBRARY_PATH для поиска текущей папки или конкретного каталога для вновь созданной динамической библиотеки.

Чтобы запустить исполняемый файл ./mytest/ и временно сказать Linux искать библиотеку в текущем каталоге, используйте следующую команду в командной строке:

LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./mytest

Исполняемые файлы, которые экспортируют символы

Если исполняемый файл имеет символы, которые должны быть доступны для других динамических библиотек, когда эти динамические библиотеки загружаются, используйте декларацию процедур со спецификатором Export, и параметр командной строки -export при создании (линковании) исполняемого файла.

Опция -export не имеет дополнительного эффекта при использовании параметров командной строки с -dylib или -dll

Загрузка динамических библиотек динамически

Динамически подключаемые библиотеки могут быть загружены и использованы во время выполнения путем динамической загрузки библиотеки и ее символов во время выполнения.

  • DyLibLoad может использоваться для загрузки и получения дескриптора динамической библиотеки.
  • DyLibSymbol используется для получения адреса символа в загруженной динамической библиотеке.
  • DyLibFree используется, чтобы выгрузить динамическую библиотеку, когда она больше не нужна.

Процедуры в динамической библиотеке должны использовать спецификатор Export для того, чтобы имена символов помещались в таблицу экспорта общего доступа библиотеки.

» mydll.bas
» компилировать как: fbc -dll mydll.bas
» Это создаст mydll.dll (и libmydll.dll.a библиотеку импорта) на Windows,
» и libmydll.so н а Linux.
»
» Примечание: libmydll.dll.a представляет собой библиотеку импорта ,
» которая нужна только при создании исполняемого файла
» Не нужно включать ее в конечный продукт,
» она бесполезна для конечных пользователей.

» Простая экспортируемая функция; Запись отключает искажение имени,
» которое в FB по умолчанию в верхнем регистре; если не использовать alias ,
» то вместо красивого названия AddNumbers() будет ADDNUMBERS()
Function AddNumbers Alias «AddNumbers» ( ByVal a As Integer , ByVal b As Integer ) As Integer Export
Function = a + b
End Function

» load.bas: Загрузка mydll.dll (или libmydll.so) во вр емя выполнения , вызывает из mydll
» одну функцию и печатает результат. mydll не требуется во время компиляции.
» компилировать как: fbc test.bas
»
» Примечание: Ожидается, что скомпилированная библиотека mydll.dll (или libmydll.so)
» будет доступна в текущем каталоге.

» Обратите внимание, что мы указываем просто «mydll» в качестве имени файла библиотеки;
» Это необходимо для обеспечения совместимости между Windows и Linux, поскольку в разных
» системах динамические библиотеки имеют р азное имя файла и расширение.
Dim As Any Ptr library = DyLibLoad ( «mydll» )
If ( library = 0 ) Then
Print «Failed to load the mydll dynamic library, aborting program. »
End 1
End If

» Этот указатель на функцию , он будет использоваться для вызова функции из mydll, после
» того, как был найден адрес. Примечание: Он должен иметь ту же конвенцию вызова и те же параметры.
Dim AddNumbers As Function ( ByVal As Integer , ByVal As Integer ) As Integer
AddNumbers = DyLibSymbol ( library , «AddNumbers» )
If ( AddNumbers = 0 ) Then
Print «Could not retrieve the AddNumbers() function’s address from the mydll library, aborting program. »
End 1
End If

Dim As Integer x = Rnd * 10
Dim As Integer y = Rnd * 10

Print x ; » +» ; y ; » oth»>; AddNumbers ( x , y )

» Закончили работать с библиотекой; ОС автоматически выгружает библиотеки,
» загруженные процессом когда процесс завершается, но мы можем также сами
» выгрузить библиотеку во время выполнения программы , тем самым освобождая
» ненужные более ресурсы. Это как раз делается в следующей строке.
» Помните, как только вы выгрузите ранее загруженную библиотеку, все символы
» полученные с помощью dylibsymbol становятся недействительными
» и доступ к ним приведет к сбою приложения.
DyLibFree ( library )

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *