dlclose(3) открывает и закрывает общий объект

Other Alias

dlopen, dlmopen

ОБЗОР

#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);

Компонуется при указании параметра -ldl.

ОПИСАНИЕ

dlopen()

Функция dlopen() загружает динамический общий объект (общую библиотеку) из файла, имя которого указано в строке filename (завершается null) и возвращает непрозрачный описатель на загруженный объект. Данный описатель используется другими функциями программного интерфейса dlopen, такими как dlsym(3), dladdr(3), dlinfo(3) и dlclose().

Если filename равно NULL, то возвращается описатель основной программы. Если filename содержит косую черту («/»), то это воспринимается как имя с путём (относительным или абсолютным). Иначе динамический компоновщик ищет объект в следующих местах (подробности смотрите в ld.so(8)):

  • (только в ELF) Если исполняемый файл вызывающей программы содержит метку DT_RPATH, т не содержит метки DT_RUNPATH, то производится поиск в каталогах, описанных в метке DT_RPATH.
  • Если при запуске программы была определена переменная окружения LD_LIBRARY_PATH, содержащая список каталогов через двоеточие, то производится поиск в этих каталогах (по соображениям безопасности эта переменная игнорируется для программ с установленными битами set-user-ID и set-group-ID).
  • (только в ELF) Если исполняемый файл вызывающей программы содержит метку DT_RUNPATH, то производится поиск по каталогам, перечисленным в этой метке.
  • Производится проверка в кэширующем файле /etc/ld.so.cache (обслуживается ldconfig(8)) на предмет наличия записи для filename.
  • Просматриваются каталоги /lib и /usr/lib (именно в таком порядке).

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

В flags должно быть одно из двух следующих значений:

RTLD_LAZY
Выполнять позднее связывание. Выполняется поиск только тех символов, на которые есть ссылки из кода. Если на символ никогда не ссылаются, то он никогда не будет разрешён (позднее связывание (lazy binding) выполняется только при ссылке на функции; ссылки на переменные всегда привязываются сразу при загрузке общего объекта). Начиная с libc 2.1.1, этот флаг заменяется на значение переменной окружения LD_BIND_NOW.
RTLD_NOW
Если указано данное значение или переменная окружения LD_BIND_NOW не пуста, то все неопределённые символы в общем объекте ищутся до возврата из dlopen(). Если этого сделать не удаётся, то возвращается ошибка.

Также в В flags может быть ноль или более значение, объединяемых по ИЛИ:

RTLD_GLOBAL
Символы, определённые в этом общем объекте, будут доступны при поиске символов, для общих объектов, загружаемых далее.
RTLD_LOCAL
Противоположность RTLD_GLOBAL, используется по умолчанию, если не задано ни одного флага. Символы, определённые в этом общем объекте, не будут доступны при разрешении ссылок для общих объектов, загружаемых далее.
RTLD_NODELETE (начиная с glibc 2.2)
Не выгружать общий объект при dlclose(). В результате статические переменные объекта не инициализируются повторно, если объект загружается снова по dlopen().
RTLD_NOLOAD (начиная с glibc 2.2)
Не загружать общий объект. Это можно использовать для тестирования того, что объект уже загружен (dlopen() возвращает NULL, если нет, или описатель объекта в противном случае). Данный флаг также можно использовать для изменения флагов уже загруженного объекта. Например, общий объект, который был загружен ранее с RTLD_LOCAL, можно открыть повторно с RTLD_NOLOAD | RTLD_GLOBAL.
RTLD_DEEPBIND (начиная с glibc 2.3.4)
Задать объекта, в котором поиск символов будет осуществляться перед поиском в области глобальных символов. Это означает, что самодостаточный объект будет использовать свои собственные символы вместо глобальных символов с тем же именем, содержащихся в объектах, которые уже были загружены.

Если значение filename равно NULL, то возвращается описатель для главной программы. При передаче в dlsym(), этот описатель вызывает поиск символа в главной программе, затем во все общих объектах, загруженных при запуске программы, и затем во всех общих объектах, загруженных dlopen() с флагом RTLD_GLOBAL.

Внешние ссылки в общем объекте разрешаются с использованием общих объектов по их списку объектных зависимостей и других объектов, ранее открытых с флагом RTLD_GLOBAL. Если исполняемый файл скомпонован с параметром «-rdynamic» (или тождественным ему «--export-dynamic»), то глобальные символы в исполняемом файле будут также использоваться при разрешении зависимостей в динамически загружаемом общем объекте.

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

Если по какой-то причине dlopen() завершается неудачно, то возвращается NULL.

dlmopen()

Данная функция делает то же самое что и dlopen(), аргументы filename и flags, а также возвращаемое значение — такие же, отличия описаны далее.

Функция dlmopen() отличается от dlopen(), главным образом в том, что имеет дополнительный аргумент lmid, в котором задаётся список карт связей (link-map list, ещё называемый пространством имён), в который должен быть загружен общий объект (dlopen() добавляет динамически загружаемый общий объект в тоже пространство имён, в котором находится общий объект, из которого был вызван dlopen()). Тип Lmid_t является закрытым описателем, который ссылается на пространство имён.

В аргументе lmid может быть указан ID существующего пространства имён (который может быть получен с помощью dlinfo(3) с запросом RTLD_DI_LMID) или одно из следующих специальных значений:

LM_ID_BASE
Загрузить общий объект в начальное пространство имён (т. е., в пространство имён приложения).
LM_ID_NEWLM
Создать новое пространство имён и загрузить в него общий объект. Объект должен быть корректно скомпонован с ссылками на все остальные общие объекты, которые ему требуются, так как новое пространство имён изначально пустое.

Если filename равно NULL, то для lmid разрешено только значение LM_ID_BASE.

dlclose()

Функция dlclose() уменьшает счётчик ссылок на динамически загружаемый общий объект, на который ссылается handle. Если счётчик ссылок достигает нуля, то объект выгружается. Все общие объекты, которые были автоматически загружены при вызове 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.

ЗАМЕЧАНИЯ

Функция dlmopen() и пространства имён

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

Функция dlmopen() позволяет достичь изоляции загружаемых объектов — загружает общий объект в новое пространство имён без показа символов всему приложению, а только новому объекту. Заметим, что использование флага RTLD_LOCAL недостаточно для этой цели, так как он делает недоступным символы общего объекта любому другому общему объекту. В некоторых случаях может понадобиться, чтобы символы динамически загружаемого общего объекта были доступны другим общим объектам (но не всем объектам) без показа этих символов всему приложению. Этого можно достичь используя отдельное пространство имён и флаг RTLD_GLOBAL.

Функцию 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.

ПРИМЕР

Программа, представленная ниже, загружает библиотеку math (glibc), ищет адрес функции cos(3) и печатает косинус 2.0. Пример сборки и выполнения программы:

$ cc dlopen_demo.c -ldl
$ ./a.out
-0.416147

Исходный код программы

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <gnu/lib-names.h>  /* определение LIBM_SO (который
                               является строкой вида 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");
    /* Согласно стандарту ISO C, преобразование между указателями на
       функции и «void *», использовавшемуся выше, приводит к
       неопределённым результатам. В POSIX.1-2003 и POSIX.1-2008
       принимается такое поведение и предлагается следующий
       обходной вариант:
           *(void **) (&cosine) = dlsym(handle, "cos");
       Такое (топорное) преобразование удовлетворяет стандарту ISO C и
       предупреждений компилятора не будет.
       Список опечаток 2013 к POSIX.1-2008 (т.н. POSIX.1-2013) улучшает
       состояние, требуя от реализаций поддержки преобразования «void *»
       в указатель на функцию. Тем не менее, некоторые компиляторы
       (например, gcc с параметром «-pedantic») могут выдавать
       предупреждение о преобразовании в этой программе. */
    error = dlerror();
    if (error != NULL) {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }
    printf("%f\n", (*cosine)(2.0));
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

ДЕФЕКТЫ

В glibc 2.21, указание флага RTLD_GLOBAL при вызове dlmopen() приводит к ошибке. Кроме этого, указание RTLD_GLOBAL при вызове dlopen() приводит к падению программы (SIGSEGV), если вызов делается из любого объекта, загруженного в пространство имён, отличное от начального пространства имён.