dlopen(3) | Library Functions Manual | dlopen(3) |
dlclose, dlopen, dlmopen - открывает и закрывает общий объект
Dynamic linking library (libdl, -ldl)
#include <dlfcn.h>
void *dlopen(const char *filename, int flags); int dlclose(void *handle);
#define _GNU_SOURCE
#include <dlfcn.h>
void *dlmopen(Lmid_t lmid, const char *filename, int flags);
Функция dlopen() загружает динамический общий объект (общую библиотеку) из файла, имя которого указано в строке filename (завершается null) и возвращает скрытный описатель на загруженный объект. Данный описатель используется другими функциями программного интерфейса dlopen, такими как dlsym(3), dladdr(3), dlinfo(3) и dlclose().
Если filename равно NULL, то возвращается описатель основной программы. Если filename содержит косую черту («/»), то это воспринимается как имя с путём (относительным или абсолютным). Иначе динамический компоновщик ищет объект в следующих местах (подробности смотрите в ld.so(8)):
Если объект, указанный filename, зависит от других общих объектов, то они также автоматически загружаются динамическим компоновщиком согласно этим же правилам (процесс может выполняться рекурсивно, если эти объекты, в свою очередь, зависят от других, и так далее).
В flags должно быть одно из двух следующих значений:
Также в В flags может быть ноль или более значение, объединяемых по ИЛИ:
If filename is NULL, then the returned handle is for the main program. When given to dlsym(3), this handle causes a search for a symbol in the main program, followed by all shared objects loaded at program startup, and then all shared objects loaded by dlopen() with the flag RTLD_GLOBAL.
Поиск символьных ссылок общего объекта производится следующим образом (в таком порядке): символы в карте ссылок объектов, загруженных для главной программы и её зависимостей; символы в общих объектах (и их зависимостях), которые были открыты ранее с помощью dlopen() и флага RTLD_GLOBAL; определения в самом общем объекте (и любых зависимостях, которые были загружены для этого объекта).
Все глобальные символы в исполняемом файле, которые были помещены в его таблицу динамических символов посредством ld(1), также могут быть использованы при поиске ссылок динамически загружаемого общего объекта. Символы могут попасть в таблицу динамических символов из-за того, что исполняемый файл был скомпонован с флагом «-rdynamic» (или его синонимом «--export-dynamic»), который помещает что все глобальные символы исполняемого файла в таблицу динамических символов, или из-за того, что ld(1) определил зависимость от символа из другого объекта при статической компоновке.
Если общий объект загружается с помощью dlopen() повторно, то возвращается тот же описатель на объект. Динамический компоновщик ведёт счётчик ссылок для описателей объектов, поэтому динамически загруженный общий объект не высвобождается dlclose() до тех пор, пока он не будет вызвана столько же раз сколько и dlopen(). Процедуры инициализации (смотрите ниже) вызываются только когда объект действительно загружается в память (т. е., когда счётчик ссылок увеличивается на 1).
Последующий вызов dlopen(), загружающий тот же общий объект с флагом RTLD_NOW, может привести к поиску символов для общего объекта ранее загруженного с флагом RTLD_LAZY. Схожим образом объект, открытый ранее с флагом RTLD_LOCAL, в последующем вызове dlopen() может быть преобразован в RTLD_GLOBAL.
Если по какой-то причине dlopen() завершается неудачно, то возвращается NULL.
This function performs the same task as dlopen()—the filename and flags arguments, as well as the return value, are the same, except for the differences noted below.
Функция dlmopen() отличается от dlopen(), главным образом в том, что имеет дополнительный аргумент lmid, в котором задаётся список карт связей (link-map list, ещё называемый пространством имён), в который должен быть загружен общий объект (dlopen() добавляет динамически загружаемый общий объект в тоже пространство имён, в котором находится общий объект, из которого был вызван dlopen()). Тип Lmid_t является скрытым описателем, который ссылается на пространство имён.
В аргументе lmid может быть указан ID существующего пространства имён (который может быть получен с помощью dlinfo(3) с запросом RTLD_DI_LMID) или одно из следующих специальных значений:
Если filename равно NULL, то для lmid разрешено только значение LM_ID_BASE.
Функция dlclose() уменьшает счётчик ссылок на динамически загружаемый общий объект, на который ссылается handle.
Если счётчик ссылок достигает нуля и символы этого объекта не нужны другим объектам, то объект выгружается после первого вызова любого деструктора, определённого в объекте (символы в этом объекте могут требоваться в другом объекте из-за того, что этот объект был открыт с флагом RTLD_GLOBAL и один из его символов совпадает с расположением из другого объекта).
Все общие объекты, которые были автоматически загружены при вызове dlopen() для объекта, на который ссылается handle, рекурсивно закрываются таким же способом.
Успешный возврат из dlclose() не гарантирует, что символы, связанные с handle удалятся из адресного пространства вызывающего. В дополнении к ссылкам, полученным из-за явного вызова dlopen(), общий объект может быть загружен неявно (и увеличится счётчик ссылок), так как от него зависят другие общие объекты. Общий объект будет удалён из адресного пространства только когда будут удалены все ссылки на него.
При успешном выполнении dlopen() и dlmopen() для загруженного объекта возвращают описатель не равный NULL. При ошибке (файл не найден, недоступен для чтения, имеет неправильный формат или возникли ошибке при загрузке) эти функции возвращают NULL.
При успешном выполнении dlclose() возвращает 0; при ошибке возвращается ненулевое значение.
Ошибки, возникшие в этих функциях, можно определить с помощью dlerror(3).
Функции dlopen() и dlclose() имеются в glibc 2.0 и новее. Функция dlmopen() впервые появилась в glibc 2.3.4.
Описание терминов данного раздела смотрите в attributes(7).
Интерфейс | Атрибут | Значение |
dlopen(), dlmopen(), dlclose() | Безвредность в нитях | MT-Safe |
В POSIX.1-2001 описаны dlclose() и dlopen(). Функция dlmopen() является расширением GNU.
Флаги RTLD_NOLOAD, RTLD_NODELETE и RTLD_DEEPBIND являются расширением GNU; первые два этих флага есть также в Solaris.
Списком карты связей задаётся изолированное пространство имён для определения символов динамическим компоновщиком. Внутри пространства имён зависимые общие объекты неявно загружаются по обычным правилам, символьные ссылки разрешаются подобным образом, но при этом учитываются только те объекты, которые были загружены (явно и неявно) в пространство имён.
The dlmopen() function permits object-load isolation—the ability to load a shared object in a new namespace without exposing the rest of the application to the symbols made available by the new object. Note that the use of the RTLD_LOCAL flag is not sufficient for this purpose, since it prevents a shared object's symbols from being available to any other shared object. In some cases, we may want to make the symbols provided by a dynamically loaded shared object available to (a subset of) other shared objects without exposing those symbols to the entire application. This can be achieved by using a separate namespace and the RTLD_GLOBAL flag.
Функцию dlmopen() также можно использовать для получения изолированности, большей чем с флагом RTLD_LOCAL. В частности, общие объекты, загруженные с RTLD_LOCAL, могут быть видимы при флаге RTLD_GLOBAL, если они зависят от другого общего объекта, загруженного с флагом RTLD_GLOBAL. То есть, RTLD_LOCAL недостаточно изолирует загружаемый общий объект, за исключением случая (редкого), где он явно контролирует зависимости всех загружаемых общих объектов.
Возможный случай применения dlmopen() — модули, где автор инфраструктуры модулей не может доверять авторам модулей и не хочет, чтобы все неопределённые символы инфраструктуры модулей определялись из модулей. Другой случай использования — загрузка одного объекта несколько раз. Без dlmopen() это потребовало бы создание отдельных копий файлов общего объекта. С помощью dlmopen() можно загрузить один файл общего объекта в разные пространства имён.
В реализации glibc поддерживается до 16 пространств имён.
Общие объекты могут экспортировать с помощью атрибутов функций __attribute__((constructor)) и __attribute__((destructor)). Функции-конструкторы выполняются перед возвратом из dlopen(), а функции-деструкторы выполняются перед возвратом из dlclose(). Общий объект может экспортировать несколько конструкторов и деструкторов, с каждой функцией может быть связан приоритет, которым определяется порядок выполнения функций. Подробней смотрите info-страницу gcc (раздел «Атрибуты функции»).
Старым способом достижения того же (частично) результата является использование двух специальных символов, распознаваемых компоновщиком: _init и _fini. Если динамически загружаемый общий объект экспортирует процедуру с именем _init(), то её код выполняется после загрузки общего объекта, но возвращения из dlopen(). Если общий объект экспортирует процедуру с именем _fini(), то её код выполняется перед выгрузкой объекта. В этом случае не должна выполняться компоновка с системными файлами начального запуска, в которых содержатся версии по умолчанию этих файлов; для этого нужно вызывать gcc(1) с параметром командной строки -nostartfiles.
Использование _init и _fini теперь не рекомендуется, используйте упомянутые конструкторы и деструкторы, которые, среди прочих преимуществ, позволяют определять многократно вызываемые функции инициализации и завершения.
Начиная с glibc 2.2.3, atexit(3) может использоваться для регистрации обработчика завершения работы, который автоматически вызывается при выгрузке общего объекта.
Эти функции являются часть программного интерфейса dlopen, возникшего в SunOS.
В glibc 2.24 указание флага RTLD_GLOBAL при вызове dlmopen() приводит к ошибке. Кроме этого, указание RTLD_GLOBAL при вызове dlopen() приводит к падению программы (SIGSEGV), если вызов делается из любого объекта, загруженного в пространство имён, отличное от начального пространства имён.
Программа, представленная ниже, загружает библиотеку math (glibc), ищет адрес функции cos(3) и печатает косинус 2.0. Пример сборки и выполнения программы:
$ cc dlopen_demo.c -ldl $ ./a.out -0.416147
#include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <gnu/lib-names.h> /* Defines LIBM_SO (which will be a
string such as "libm.so.6") */ int main(void) {
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen(LIBM_SO, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror(); /* Очистка всех результатов ошибок */
cosine = (double (*)(double)) dlsym(handle, "cos");
/* According to the ISO C standard, casting between function
pointers and 'void *', as done above, produces undefined results.
POSIX.1-2001 and POSIX.1-2008 accepted this state of affairs and
proposed the following workaround:
*(void **) (&cosine) = dlsym(handle, "cos");
Такое (топорное) преобразование удовлетворяет стандарту ISO C и
предупреждений компилятора не будет.
The 2013 Technical Corrigendum 1 to POSIX.1-2008 improved matters
by requiring that conforming implementations support casting
'void *' to a function pointer. Nevertheless, some compilers
(e.g., gcc with the '-pedantic' option) may complain about the
cast used in this program. */
error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("%f\n", (*cosine)(2.0));
dlclose(handle);
exit(EXIT_SUCCESS); }
ld(1), ldd(1), pldd(1), dl_iterate_phdr(3), dladdr(3), dlerror(3), dlinfo(3), dlsym(3), rtld-audit(7), ld.so(8), ldconfig(8)
Страницы в формате Info для gcc и ld
Русский перевод этой страницы руководства был сделан Yuri Kozlov <yuray@komyakino.ru> и Иван Павлов <pavia00@gmail.com>
Этот перевод является бесплатной документацией; прочитайте Стандартную общественную лицензию GNU версии 3 или более позднюю, чтобы узнать об условиях авторского права. Мы не несем НИКАКОЙ ОТВЕТСТВЕННОСТИ.
Если вы обнаружите ошибки в переводе этой страницы руководства, пожалуйста, отправьте электронное письмо на man-pages-ru-talks@lists.sourceforge.net.
5 февраля 2023 г. | Linux man-pages 6.03 |