dlopen(3)

NAZWA
dlclose, dlopen, dlmopen - otwiera i zamyka obiekt dzielony

BIBLIOTEKA
Biblioteka konsolidacji dynamicznej (libdl, -ldl)

SKŁADNIA
#include <dlfcn.h>

void *dlopen(const char *path, int flags);
int dlclose(void *handle);

#define _GNU_SOURCE
#include <dlfcn.h>

void *dlmopen(Lmid_t lmid, const char *path, int flags);

OPIS

  dlopen()
Funkcja dlopen() ładuje dynamiczny obiekt dzielony (bibliotekę dzieloną) pliku, o nazwie zawartej w zakończonym znakiem nul łańcuchu path i zwraca nieprzezroczysty „uchwyt” dla tego obiektu dzielonego. Uchwytu tego używa się z innymi funkcjami z API dlopen, takimi jak dlsym(3), dladdr(3), dlinfo(3) i dlclose().

Jeśli path wynosi NULL, to zwracany jest uchwyt do głównego programu. Jeśli path zawiera ukośnik („/”), to jest interpretowana jako (względna lub absolutna) ścieżka. W innych sytuacjach, konsolidator dynamiczny szuka obiektu w następujący sposób (więcej szczegółów w podręczniku ld.so(8)):

•   (tylko ELF) Jeśli wywołujący obiekt (tj. biblioteka współdzielona, lub plik wykonywalny, z którego wywoływane jest dlopen()) zawiera znacznik DT_RPATH, natomiast nie zawiera znacznika DT_RUNPATH, to przeszukiwane są katalogi wypisane w znaczniku DT_RPATH.

•   Jeśli, w momencie uruchomienia tego programu, zdefiniowano zmienną środowiskową LD_LIBRARY_PATH i zawierała ona listę katalogów rozdzielonych dwukropkiem, to przeszukiwane są te katalogi (ze względów bezpieczeństwa, zmienna ta jest ignorowana w przypadku programów z ustawionym bitem set-user-ID lub set-group-ID).

•   (tylko ELF) Jeśli wywołujący obiekt zawiera znacznik DT_RUNPATH, to przeszukiwane są katalogi wypisane w tym znaczniku.

•   Sprawdzany jest plik bufora /etc/ld.so.cache (zarządzany przez ldconfig(8)) pod kątem zawierania wpisu dla path.

•   Przeszukiwane są katalogi /lib i /usr/lib (w tej kolejności).

Jeśli obiekt określony w path ma zależności od innych obiektów dzielonych, są one również automatycznie ładowane przez konsolidator dynamiczny, za pomocą tych samych reguł (proces ten może postępować rekurencyjnie, jeśli te obiekty mają z kolei swoje zależności itd.).

We flags należy podać jedną z dwóch poniższych wartości:

RTLD_LAZY
    Przeprowadza leniwe dowiązanie. Rozwiązuje symbole jedynie wtedy, gdy wykonywany jest kod, który się do nich odnosi. Jeśli do danego symbolu nic się nie odniesie, to nie zostanie nigdy rozwiązany (leniwe dowiązanie jest wykonywane tylko przy odniesieniach do funkcji; odniesienia do zmiennych są zawsze bezzwłocznie przypisywane przy ładowaniu obiektu dzielonego). Od glibc 2.1.1, znacznik ten jest przesłaniany przez efekt zmiennej środowiskowej LD_BIND_NOW.

RTLD_NOW
    Jeśli poda się tę wartość lub zmienna środowiskowa LD_BIND_NOW jest ustawiona na niepusty łańcuch, wszystkie niezdefiniowane symbole obiektu dzielonego są rozwiązywane przed powrotem dlopen(). Jeśli nie da się tego wykonać, zwracany jest błąd.

Ponadto, we flags można zsumować (OR) zero lub więcej z poniższych wartości:

RTLD_GLOBAL
    Symbole zdefiniowane za pomocą tego obiektu dzielonego zostaną udostępnione do rozwiązywania symboli kolejno ładowanych obiektów dzielonych.

RTLD_LOCAL
    Jest to odwrotność RTLD_GLOBAL, a także wartość domyślna, jeśli nie podano żadnego znacznika. Symbole zdefiniowane w tym obiekcie dzielonym nie są udostępniane do rozwiązywania symboli kolejno ładowanych obiektów dzielonych.

RTLD_NODELETE (od glibc 2.2)
    Nie odłącza obiektu dzielonego podczas dlclose(). Statyczne i globalne zmienne obiektu nie są zatem inicjowane ponownie, jeśli obiekt zostanie później przeładowany za pomocą dlopen().

RTLD_NOLOAD (od glibc 2.2)
    Nie ładuje obiektu dzielonego. Można to wykorzystać do sprawdzenia, czy obiekt jest już rezydentny (dlopen() zwróci NULL jeśli nie jest, albo uchwyt obiektu jeśli jest). Znacznik może posłużyć również do dodania znaczników do już załadowanego obiektu dzielonego. Przykładowo, wcześniej załadowany obiekt dzielony ze znacznikiem RTLD_LOCAL może być otwarty ponownie z RTLD_NOLOAD | RTLD_GLOBAL.

RTLD_DEEPBIND (od glibc 2.3.4)
    Umieszcza dziedzinę przeszukiwania dla symboli, w tym obiekcie dzielonym przed dziedziną globalną. Oznacza to, że samodzielny obiekt będzie preferował używanie swoich symboli, zamiast symboli globalnych o tej samej nazwie w obiektach, które zostały już załadowane.

Jeśli path wynosi NULL, to zwracany jest uchwyt do głównego programu. Przy przekazaniu tego uchwytu do dlsym(3), powoduje on wyszukiwanie symbolu w głównym programie, następnie we wszystkich obiektach dzielonych załadowanych przy rozruchu programu, a potem we wszystkich obiektach dzielonych załadowanych za pomocą dlopen() ze znacznikiem RTLD_GLOBAL.

Odniesienia symboli w obiekcie dzielonym są rozwiązywane za pomocą (w tej kolejności): symboli w mapie linkowań obiektów załadowanych do głównego programu i jego zależności; symboli w obiektach dzielonych (i ich zależności), które były uprzednio otworzone za pomocą dlopen() i znacznika RTLD_GLOBAL; definicji w samym obiekcie dzielonym (i zależności, które zostały załadowane dla tego obiektu).

Wszelkie symbole globalne w pliku wykonywalnym, które zostały umieszczone w jego tablicy symboli dynamicznych przez ld(1), mogą być użyte również do rozwiązywania odniesień w dynamicznie załadowanym obiekcie dzielonym. Symbole mogą być umieszczone w tablicy symboli dynamicznych albo ponieważ plik wykonywalny skonsolidowano z „-rdynamic” (lub synonimicznie: z „--export-dynamic”), co powoduje umieszczenie wszystkich symboli globalnych pliku wykonywalnego w tablicy symboli dynamicznych, albo z powodu odnotowania przez ld(1) zależności od symbolu w innym obiekcie, podczas konsolidowania statycznego.

Jeśli ten sam obiekt dzielony zostanie powtórnie otworzony za pomocą dlopen(), to zwracany jest ten sam uchwyt obiektu. Dynamiczny konsolidator utrzymuje liczniki odniesień dla uchwytów obiektów, tak więc dynamicznie załadowany obiekt dzielony nie jest dealokowany, dopóki dlclose() nie zostanie na nim wywołany tak wiele razy, jak udanie wywołano na nim dlopen(). Konstruktory (zob. niżej) są wywoływane tylko wtedy, gdy obiekt jest faktycznie ładowany do pamięci (tj. gdy licznik odniesień wzrośnie do 1).

Kolejne wywołanie dlopen(), ładujące ten sam obiekt za pomocą RTLD_NOW, może wymusić rozwiązanie symbolu dla obiektu dzielonego wcześniej załadowanego za pomocą RTLD_LAZY. Podobnie, obiekt uprzednio załadowany za pomocą RTLD_LOCAL może awansować na RTLD_GLOBAL w kolejnym wywołaniu dlopen().

Jeśli dlopen() z jakiegoś powodu zawiedzie, zwróci NULL.

  dlmopen()
Funkcja ta wykonuje te same zadania co dlopen() — argumenty path i flags oraz wartości zwraca są takie same, z wyjątkiem różnic opisanych niżej.

Funkcja dlmopen() różni się od dlopen() głównie tym, że akceptuje dodatkowy argument, lmid, który określa listę map linkowań (link-map) (do której można odnosić się również jako namespace), w której obiekt dzielony powinien być załadowany (dla porównania, dlopen() dodaje dynamicznie ładowany obiekt dzielony do tej samej przestrzeni nazw, jak obiekt dzielony, z którego wywołano dlopen()). Typ Lmid_t jest nieprzezroczystym uchwytem odnoszącym się do tej przestrzeni nazw.

Argument lmid jest albo identyfikatorem istniejącej przestrzeni nazw (który można pozyskać za pomocą żądania RTLD_DI_LMID dlinfo(3)) lub jedną z następujących wartości specjalnych:

LM_ID_BASE
    Ładuje obiekt dzielony w pierwotnej przestrzeni nazw (tj. w przestrzeni nazw aplikacji).

LM_ID_NEWLM
    Tworzy nową przestrzeń nazw i ładuje w niej obiekt dzielony. Obiekt musi być prawidłowo skonsolidowany do odniesień wszystkich innych obiektów dzielonych jakich wymaga, ponieważ nowa przestrzeń nazw jest początkowo pusta.

Jeśli path wynosi NULL, to jedyną dopuszczalną wartością lmid jest LM_ID_BASE.

  dlclose()
Funkcja dlclose() zmniejsza licznik odniesień na dynamicznie załadowanych obiektach, do których odnosi się handle.

Jeśli licznik odniesień obiektu spadnie do zera, a żadne symbole w tym obiekcie nie są wymagane przez inne obiekty, obiekt ten jest odłączany po pierwszym wywołaniu dowolnych destruktorów zdefiniowanych dla obiektu (symbole w obiekcie mogą być wymagane przez inny obiekt, ponieważ otworzono go ze znacznikiem RTLD_GLOBAL, a jeden z jego symboli umożliwił relokację w innym obiekcie).

Wszystkie obiekty dzielone załadowane automatycznie, gdy dlopen() przywołano na obiekcie, do którego odnosi się handle, są rekurencyjnie zamykane w ten sam sposób.

Pomyślny powrót z dlclose() nie gwarantuje, że symbole powiązane z handle są usunięte z przestrzeni adresowej wywołującego. Oprócz odniesień wynikających z bezpośrednich wywołań dlopen(), mogły zostać niebezpośrednio załadowane obiekty dzielone (i policzone odniesienia), ze względu na zależności w innych obiektach dzielonych. Tylko gdy wszystkie odniesienia zostały uwolnione, obiekt dzielony może być usunięty z przestrzeni adresowej.

WARTOŚĆ ZWRACANA
W przypadku powodzenia, dlopen() i dlmopen() zwracają uchwyt do ładowanego obiektu, niebędący NULL-em. W razie wystąpienia błędu (nie można znaleźć pliku, nie był odczytywalny, miał zły format lub spowodował błędy przy załadowaniu) funkcje te zwracają NULL.

W przypadku powodzenia, dlclose() zwraca 0; w razie wystąpienia błędu, zwraca wartość niezerową.

Błędy z opisywanych funkcji można diagnozować za pomocą dlerror(3).

ATRYBUTY
Informacje o pojęciach używanych w tym rozdziale można znaleźć w podręczniku attributes(7).
Interfejs | Atrybut | Wartość
dlopen(), dlmopen(), dlclose() | Bezpieczeństwo wątkowe | MT-bezpieczne

STANDARDY

dlopen()
dlclose()
    POSIX.1-2008.

dlmopen()
RTLD_NOLOAD
RTLD_NODELETE
    GNU.

RTLD_DEEPBIND
    Solaris.

HISTORIA

dlopen()
dlclose()
    glibc 2.0. POSIX.1-2001.

dlmopen()
    glibc 2.3.4.

UWAGI

  dlmopen() i przestrzenie nazw
Lista map linków (link-map) definiuje izolowaną przestrzeń nazw do rozwiązywania symboli przez konsolidator dynamiczny. Wewnątrz przestrzeni nazw, zależny obiekt dzielony jest niebezpośrednio ładowany zgodnie ze zwykłymi regułami, a odniesienia do symboli są podobnie rozwiązywane zgodnie ze zwykłymi regułami, lecz rozwiązywanie to jest ograniczone do definicji zapewnionych przez obiekty, które zostały (bezpośrednio lub niebezpośrednio) załadowane do tej przestrzeni nazw.

Funkcja dlmopen() pozwala na izolację załadowań obiektów — możliwość załadowania obiektu dzielonego do nowej przestrzeni nazw, bez ujawniania reszcie aplikacji symboli udostępnionych przez nowy obiekt. Proszę zauważyć, że użycie znacznika RTLD_LOCAL nie jest wystarczające do tego celu, ponieważ zapobiega ono udostępnieniu symboli obiektu dzielonego dla wszystkich innych obiektów dzielonych. W niektórych przypadkach, zachodzi potrzeba udostępnienia symboli zapewnionych przez dynamicznie załadowany obiekt dzielony dla (podzbioru) innych obiektów dzielonych, bez ujawniania tych symboli całej aplikacji. Można to uzyskać za pomocą oddzielnej przestrzeni nazw i znacznika RTLD_GLOBAL.

Funkcja dlmopen() może służyć również do zapewnienia lepszej izolacji, niż znacznik RTLD_LOCAL. W szczególności, obiekty dzielone załadowane za pomocą RTLD_LOCAL mogą być awansowane do RTLD_GLOBAL, jeśli są zależnościami innego obiektu dzielonego załadowanego ze znacznikiem RTLD_GLOBAL. Z tego względu, RTLD_LOCAL jest niewystarczające do izolowania załadowanego obiektu dzielonego, z wyjątkiem (nieczęstej) sytuacji, gdy ma się całkowitą kontrolę nad wszystkich zależnościami obiektu dzielonego.

Możliwe zastosowania dlmopen() to wtyczki, gdzie autor struktury ładowania wtyczek nie może ufać autorom wtyczek i nie chce, aby jakiekolwiek niezdefiniowane symbole ze struktury wtyczek były rozwiązywane do symboli wtyczek. Kolejne zastosowanie, to ładowanie tego samego obiektu więcej niż raz. Bez korzystania z dlmopen(), wymagałoby to utworzenia odrębnej kopii pliku obiektu dzielonego. Za pomocą dlmopen(), można to uzyskać ładując ten sam plik obiektu dzielonego do różnych przestrzeni nazw.

Implementacja glibc obsługuje co najwyżej 16 przestrzeni nazw.

  Funkcje inicjalizacji i kończenia
Obiekty dzielone mogą eksportować funkcje za pomocą atrybutów funkcji __attribute__((constructor)) i __attribute__((destructor)). Funkcje konstrukcyjne są wykonywane przed powrotem dlopen(), a funkcje destrukcyjne są wykonywane przez powrotem dlclose(). Obiekty dzielone mogą eksportować wiele konstruktorów i destruktorów, a każdej funkcji można przypisać priorytety, aby określić kolejność wykonywania. Więcej informacji na stronach info gcc (w rozdziale „Function attributes”).

Starszą metodą uzyskiwania (częściowo) tego samego wyniku jest użycie dwóch symboli specjalnych rozpoznawanych przez konsolidator: _init i _fini. Jeśli dynamicznie ładowany obiekt dzielony eksportuje algorytm nazwany _init(), to kod ten jest wykonywany po załadowaniu obiektu dzielonego, przed powrotem dlopen(). Jeśli obiekt dzielony eksportuje algorytm nazwany _fini(), to jest on wywoływany zaraz przed odłączeniem obiektu. W tym przypadku, konieczne jest unikania linkowania wobec rozruchowych plików systemowych, które zawierają domyślne wersje tych plików; można to uczynić za pomocą opcji wiersza poleceń gcc(1) -nostartfiles.

Używanie _init i _fini jest obecnie przestarzałe, na korzyść opisanych wcześniej konstruktorów i destruktorów, które oprócz innych zalet, pozwalają na zdefiniowanie wielu funkcji inicjalizacji i kończenia.

Od glibc 2.2.3, atexit(3) może posłużyć do zarejestrowania uchwytu wyjścia, który jest automatycznie wywoływany przez odłączaniu obiektu dzielonego.

  Historia
Funkcje te są częścią API dlopen, wywodzącego się z SunOS.

USTERKI
Według stanu na glibc 2.24, podanie znacznika RTLD_GLOBAL przy wywoływaniu dlmopen() generuje błąd. Co więcej, podanie RTLD_GLOBAL przy wywoływaniu dlopen() powoduje załamanie programu (SIGSEGV), jeśli wywołanie nastąpiło z dowolnego obiektu załadowanego w przestrzeni nazw innej niż pierwotna przestrzeń nazw.

PRZYKŁADY
Poniższy program ładuje bibliotekę matematyczną (glibc), szuka adresu funkcji cos(3) i wypisuje cosinus liczby 2.0. Pokazano przykład tworzenia i działania programu:

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

  Kod źródłowy programu
    #include <dlfcn.h>
    #include <stdio.h>
    #include <stdlib.h>

    #include <gnu/lib-names.h>  /* Definiuje LIBM_SO (będzie to
                                   łańcuch podobny do "libm.so.6") */
    int
    main(void)
    {
        void *handle;
        typeof(double (double)) *cosine;
        char *error;

        handle = dlopen(LIBM_SO, RTLD_LAZY);
        if (!handle) {
            fprintf(stderr, "%s\n", dlerror());
            exit(EXIT_FAILURE);
        }

        dlerror();    /* Czyści ewentualne istniejące błędy */

        cosine = (typeof(double (double)) *) dlsym(handle, "cos");

        /* Zgodnie ze standardem ISO C, rzutowanie pomiędzy wskaźnikami
           funkcji a 'void *', jak poniżej, daje niezdefiniowany wynik.
           POSIX.1-2001 i POSIX.1-2008 akceptują ten stan rzeczy i proponują
           następujące obejście problemu:

               *(void **) &cosine = dlsym(handle, "cos");

           To (niezgrabne) rzutowanie jest zgodne ze standem ISO C i uniknie
           ostrzeżeń kompilatora.

           2013 Technical Corrigendum 1 do POSIX.1-2008 poprawia stan rzeczy,
           wymagając od zgodnych implementacji obsługi rzutowań 'void *'
           do wskaźnika funkcji. Tym niemniej, niektóre kompilatory (np. gcc
           z opcją '-pedantic') mogą narzekać na rzutowanie użyte
           w tym programie.  */

        error = dlerror();
        if (error != NULL) {
            fprintf(stderr, "%s\n", error);
            exit(EXIT_FAILURE);
        }

        printf("%f\n", (*cosine)(2.0));
        dlclose(handle);
        exit(EXIT_SUCCESS);
    }

ZOBACZ TAKŻE
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)

strony info gcc, strony info ld

TŁUMACZENIE
Tłumaczenie niniejszej strony podręcznika: Przemek Borys <pborys@dione.ids.pl>, Andrzej Krzysztofowicz <ankry@green.mf.pg.gda.pl> i Michał Kułach <michal.kulach@gmail.com>

Niniejsze tłumaczenie jest wolną dokumentacją. Bliższe informacje o warunkach licencji można uzyskać zapoznając się z GNU General Public License w wersji 3 (https://www.gnu.org/licenses/gpl-3.0.html) lub nowszej. Nie przyjmuje się ŻADNEJ ODPOWIEDZIALNOŚCI.

Błędy w tłumaczeniu strony podręcznika prosimy zgłaszać na adres listy dyskusyjnej manpages-pl-list@lists.sourceforge.net.