dlopen(3) Library Functions Manual dlopen(3) NAZWA dlclose, dlopen, dlmopen - otwiera i zamyka obiekt dzielony BIBLIOTEKA Biblioteka konsolidacji dynamicznej (libdl, -ldl) SKLADNIA #include void *dlopen(const char *filename, int flags); int dlclose(void *handle); #define _GNU_SOURCE #include void *dlmopen(Lmid_t lmid, const char *filename, int flags); OPIS dlopen() Funkcja dlopen() laduje dynamiczny obiekt dzielony (biblioteke dzielona) pliku, o nazwie zawartej w zakonczonym znakiem nul lancuchu filename i zwraca nieprzezroczysty ,,uchwyt" dla tego obiektu dzielonego. Uchwytu tego uzywa sie z innymi funkcjami z API dlopen, takimi jak dlsym(3), dladdr(3), dlinfo(3) i dlclose(). Jesli filename wynosi NULL, to zwracany jest uchwyt do glownego programu. Jesli filename zawiera ukosnik (,,/"), to jest interpretowana jako (wzgledna lub absolutna) sciezka. W innych sytuacjach, konsolidator dynamiczny szuka obiektu w nastepujacy sposob (wiecej szczegolow w podreczniku ld.so(8)): o (tylko ELF) Jesli wywolujacy obiekt (tj. biblioteka wspoldzielona, lub plik wykonywalny, z ktorego wywolywane jest dlopen()) zawiera znacznik DT_RPATH, natomiast nie zawiera znacznika DT_RUNPATH, to przeszukiwane sa katalogi wypisane w znaczniku DT_RPATH. o Jesli, w momencie uruchomienia tego programu, zdefiniowano zmienna srodowiskowa LD_LIBRARY_PATH i zawierala ona liste katalogow rozdzielonych dwukropkiem, to przeszukiwane sa te katalogi (ze wzgledow bezpieczenstwa, zmienna ta jest ignorowana w przypadku programow z ustawionym bitem set-user-ID lub set-group-ID). o (tylko ELF) Jesli wywolujacy obiekt zawiera znacznik DT_RUNPATH, to przeszukiwane sa katalogi wypisane w tym znaczniku. o Sprawdzany jest plik bufora /etc/ld.so.cache (zarzadzany przez ldconfig(8)) pod katem zawierania wpisu dla filename. o Przeszukiwane sa katalogi /lib i /usr/lib (w tej kolejnosci). Jesli obiekt okreslony w filename ma zaleznosci od innych obiektow dzielonych, sa one rowniez automatycznie ladowane przez konsolidator dynamiczny, za pomoca tych samych regul (proces ten moze postepowac rekurencyjnie, jesli te obiekty maja z kolei swoje zaleznosci itd.). We flags nalezy podac jedna z dwoch ponizszych wartosci: RTLD_LAZY Przeprowadza leniwe dowiazanie. Rozwiazuje symbole jedynie wtedy, gdy wykonywany jest kod, ktory sie do nich odnosi. Jesli do danego symbolu nic sie nie odniesie, to nie zostanie nigdy rozwiazany (leniwe dowiazanie jest wykonywane tylko przy odniesieniach do funkcji; odniesienia do zmiennych sa zawsze bezzwlocznie przypisywane przy ladowaniu obiektu dzielonego). Od glibc 2.1.1, znacznik ten jest przeslaniany przez efekt zmiennej srodowiskowej LD_BIND_NOW. RTLD_NOW Jesli poda sie te wartosc lub zmienna srodowiskowa LD_BIND_NOW jest ustawiona na niepusty lancuch, wszystkie niezdefiniowane symbole obiektu dzielonego sa rozwiazywane przed powrotem dlopen(). Jesli nie da sie tego wykonac, zwracany jest blad. Ponadto, we flags mozna zsumowac (OR) zero lub wiecej z ponizszych wartosci: RTLD_GLOBAL Symbole zdefiniowane za pomoca tego obiektu dzielonego zostana udostepnione do rozwiazywania symboli kolejno ladowanych obiektow dzielonych. RTLD_LOCAL Jest to odwrotnosc RTLD_GLOBAL, a takze wartosc domyslna, jesli nie podano zadnego znacznika. Symbole zdefiniowane w tym obiekcie dzielonym nie sa udostepniane do rozwiazywania symboli kolejno ladowanych obiektow dzielonych. RTLD_NODELETE (od glibc 2.2) Nie odlacza obiektu dzielonego podczas dlclose(). Statyczne i globalne zmienne obiektu nie sa zatem inicjowane ponownie, jesli obiekt zostanie pozniej przeladowany za pomoca dlopen(). RTLD_NOLOAD (od glibc 2.2) Nie laduje obiektu dzielonego. Mozna to wykorzystac do sprawdzenia, czy obiekt jest juz rezydentny (dlopen() zwroci NULL jesli nie jest, albo uchwyt obiektu jesli jest). Znacznik moze posluzyc rowniez do dodania znacznikow do juz zaladowanego obiektu dzielonego. Przykladowo, wczesniej zaladowany obiekt dzielony ze znacznikiem RTLD_LOCAL moze byc otwarty ponownie z RTLD_NOLOAD | RTLD_GLOBAL. RTLD_DEEPBIND (od glibc 2.3.4) Umieszcza dziedzine przeszukiwania dla symboli, w tym obiekcie dzielonym przed dziedzina globalna. Oznacza to, ze samodzielny obiekt bedzie preferowal uzywanie swoich symboli, zamiast symboli globalnych o tej samej nazwie w obiektach, ktore zostaly juz zaladowane. Jesli filename wynosi NULL, to zwracany jest uchwyt do glownego programu. Przy przekazaniu tego uchwytu do dlsym(3), powoduje on wyszukiwanie symbolu w glownym programie, nastepnie we wszystkich obiektach dzielonych zaladowanych przy rozruchu programu, a potem we wszystkich obiektach dzielonych zaladowanych za pomoca dlopen() ze znacznikiem RTLD_GLOBAL. Odniesienia symboli w obiekcie dzielonym sa rozwiazywane za pomoca (w tej kolejnosci): symboli w mapie linkowan obiektow zaladowanych do glownego programu i jego zaleznosci; symboli w obiektach dzielonych (i ich zaleznosci), ktore byly uprzednio otworzone za pomoca dlopen() i znacznika RTLD_GLOBAL; definicji w samym obiekcie dzielonym (i zaleznosci, ktore zostaly zaladowane dla tego obiektu). Wszelkie symbole globalne w pliku wykonywalnym, ktore zostaly umieszczone w jego tablicy symboli dynamicznych przez ld(1), moga byc uzyte rowniez do rozwiazywania odniesien w dynamicznie zaladowanym obiekcie dzielonym. Symbole moga byc umieszczone w tablicy symboli dynamicznych albo poniewaz 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) zaleznosci od symbolu w innym obiekcie, podczas konsolidowania statycznego. Jesli ten sam obiekt dzielony zostanie powtornie otworzony za pomoca dlopen(), to zwracany jest ten sam uchwyt obiektu. Dynamiczny konsolidator utrzymuje liczniki odniesien dla uchwytow obiektow, tak wiec dynamicznie zaladowany obiekt dzielony nie jest dealokowany, dopoki dlclose() nie zostanie na nim wywolany tak wiele razy, jak udanie wywolano na nim dlopen(). Konstruktory (zob. nizej) sa wywolywane tylko wtedy, gdy obiekt jest faktycznie ladowany do pamieci (tj. gdy licznik odniesien wzrosnie do 1). Kolejne wywolanie dlopen(), ladujace ten sam obiekt za pomoca RTLD_NOW, moze wymusic rozwiazanie symbolu dla obiektu dzielonego wczesniej zaladowanego za pomoca RTLD_LAZY. Podobnie, obiekt uprzednio zaladowany za pomoca RTLD_LOCAL moze awansowac na RTLD_GLOBAL w kolejnym wywolaniu dlopen(). Jesli dlopen() z jakiegos powodu zawiedzie, zwroci NULL. dlmopen() Funkcja ta wykonuje te same zadania co dlopen() -- argumenty filename i flags oraz wartosci zwraca sa takie same, z wyjatkiem roznic opisanych nizej. Funkcja dlmopen() rozni sie od dlopen() glownie tym, ze akceptuje dodatkowy argument, lmid, ktory okresla liste map linkowan (link-map) (do ktorej mozna odnosic sie rowniez jako namespace), w ktorej obiekt dzielony powinien byc zaladowany (dla porownania, dlopen() dodaje dynamicznie ladowany obiekt dzielony do tej samej przestrzeni nazw, jak obiekt dzielony, z ktorego wywolano dlopen()). Typ Lmid_t jest nieprzezroczystym uchwytem odnoszacym sie do tej przestrzeni nazw. Argument lmid jest albo identyfikatorem istniejacej przestrzeni nazw (ktory mozna pozyskac za pomoca zadania RTLD_DI_LMID dlinfo(3)) lub jedna z nastepujacych wartosci specjalnych: LM_ID_BASE Laduje obiekt dzielony w pierwotnej przestrzeni nazw (tj. w przestrzeni nazw aplikacji). LM_ID_NEWLM Tworzy nowa przestrzen nazw i laduje w niej obiekt dzielony. Obiekt musi byc prawidlowo skonsolidowany do odniesien wszystkich innych obiektow dzielonych jakich wymaga, poniewaz nowa przestrzen nazw jest poczatkowo pusta. Jesli filename wynosi NULL, to jedyna dopuszczalna wartoscia lmid jest LM_ID_BASE. dlclose() Funkcja dlclose() zmniejsza licznik odniesien na dynamicznie zaladowanych obiektach, do ktorych odnosi sie handle. Jesli licznik odniesien obiektu spadnie do zera, a zadne symbole w tym obiekcie nie sa wymagane przez inne obiekty, obiekt ten jest odlaczany po pierwszym wywolaniu dowolnych destruktorow zdefiniowanych dla obiektu (symbole w obiekcie moga byc wymagane przez inny obiekt, poniewaz otworzono go ze znacznikiem RTLD_GLOBAL, a jeden z jego symboli umozliwil relokacje w innym obiekcie). Wszystkie obiekty dzielone zaladowane automatycznie, gdy dlopen() przywolano na obiekcie, do ktorego odnosi sie handle, sa rekurencyjnie zamykane w ten sam sposob. Pomyslny powrot z dlclose() nie gwarantuje, ze symbole powiazane z handle sa usuniete z przestrzeni adresowej wywolujacego. Oprocz odniesien wynikajacych z bezposrednich wywolan dlopen(), mogly zostac niebezposrednio zaladowane obiekty dzielone (i policzone odniesienia), ze wzgledu na zaleznosci w innych obiektach dzielonych. Tylko gdy wszystkie odniesienia zostaly uwolnione, obiekt dzielony moze byc usuniety z przestrzeni adresowej. WARTOSC ZWRACANA W przypadku powodzenia, dlopen() i dlmopen() zwracaja uchwyt do ladowanego obiektu, niebedacy NULL-em. W razie wystapienia bledu (nie mozna znalezc pliku, nie byl odczytywalny, mial zly format lub spowodowal bledy przy zaladowaniu) funkcje te zwracaja NULL. W przypadku powodzenia, dlclose() zwraca 0; w razie wystapienia bledu, zwraca wartosc niezerowa. Bledy z opisywanych funkcji mozna diagnozowac za pomoca dlerror(3). ATRYBUTY Informacje o pojeciach uzywanych w tym rozdziale mozna znalezc w podreczniku attributes(7). +---------------------------+--------------------------+---------------+ |Interfejs | Atrybut | Wartosc | +---------------------------+--------------------------+---------------+ |dlopen(), dlmopen(), | Bezpieczenstwo watkowe | MT-bezpieczne | |dlclose() | | | +---------------------------+--------------------------+---------------+ 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 linkow (link-map) definiuje izolowana przestrzen nazw do rozwiazywania symboli przez konsolidator dynamiczny. Wewnatrz przestrzeni nazw, zalezny obiekt dzielony jest niebezposrednio ladowany zgodnie ze zwyklymi regulami, a odniesienia do symboli sa podobnie rozwiazywane zgodnie ze zwyklymi regulami, lecz rozwiazywanie to jest ograniczone do definicji zapewnionych przez obiekty, ktore zostaly (bezposrednio lub niebezposrednio) zaladowane do tej przestrzeni nazw. Funkcja dlmopen() pozwala na izolacje zaladowan obiektow -- mozliwosc zaladowania obiektu dzielonego do nowej przestrzeni nazw, bez ujawniania reszcie aplikacji symboli udostepnionych przez nowy obiekt. Prosze zauwazyc, ze uzycie znacznika RTLD_LOCAL nie jest wystarczajace do tego celu, poniewaz zapobiega ono udostepnieniu symboli obiektu dzielonego dla wszystkich innych obiektow dzielonych. W niektorych przypadkach, zachodzi potrzeba udostepnienia symboli zapewnionych przez dynamicznie zaladowany obiekt dzielony dla (podzbioru) innych obiektow dzielonych, bez ujawniania tych symboli calej aplikacji. Mozna to uzyskac za pomoca oddzielnej przestrzeni nazw i znacznika RTLD_GLOBAL. Funkcja dlmopen() moze sluzyc rowniez do zapewnienia lepszej izolacji, niz znacznik RTLD_LOCAL. W szczegolnosci, obiekty dzielone zaladowane za pomoca RTLD_LOCAL moga byc awansowane do RTLD_GLOBAL, jesli sa zaleznosciami innego obiektu dzielonego zaladowanego ze znacznikiem RTLD_GLOBAL. Z tego wzgledu, RTLD_LOCAL jest niewystarczajace do izolowania zaladowanego obiektu dzielonego, z wyjatkiem (nieczestej) sytuacji, gdy ma sie calkowita kontrole nad wszystkich zaleznosciami obiektu dzielonego. Mozliwe zastosowania dlmopen() to wtyczki, gdzie autor struktury ladowania wtyczek nie moze ufac autorom wtyczek i nie chce, aby jakiekolwiek niezdefiniowane symbole ze struktury wtyczek byly rozwiazywane do symboli wtyczek. Kolejne zastosowanie, to ladowanie tego samego obiektu wiecej niz raz. Bez korzystania z dlmopen(), wymagaloby to utworzenia odrebnej kopii pliku obiektu dzielonego. Za pomoca dlmopen(), mozna to uzyskac ladujac ten sam plik obiektu dzielonego do roznych przestrzeni nazw. Implementacja glibc obsluguje co najwyzej 16 przestrzeni nazw. Funkcje inicjalizacji i konczenia Obiekty dzielone moga eksportowac funkcje za pomoca atrybutow funkcji __attribute__((constructor)) i __attribute__((destructor)). Funkcje konstrukcyjne sa wykonywane przed powrotem dlopen(), a funkcje destrukcyjne sa wykonywane przez powrotem dlclose(). Obiekty dzielone moga eksportowac wiele konstruktorow i destruktorow, a kazdej funkcji mozna przypisac priorytety, aby okreslic kolejnosc wykonywania. Wiecej informacji na stronach info gcc (w rozdziale ,,Function attributes"). Starsza metoda uzyskiwania (czesciowo) tego samego wyniku jest uzycie dwoch symboli specjalnych rozpoznawanych przez konsolidator: _init i _fini. Jesli dynamicznie ladowany obiekt dzielony eksportuje algorytm nazwany _init(), to kod ten jest wykonywany po zaladowaniu obiektu dzielonego, przed powrotem dlopen(). Jesli obiekt dzielony eksportuje algorytm nazwany _fini(), to jest on wywolywany zaraz przed odlaczeniem obiektu. W tym przypadku, konieczne jest unikania linkowania wobec rozruchowych plikow systemowych, ktore zawieraja domyslne wersje tych plikow; mozna to uczynic za pomoca opcji wiersza polecen gcc(1) -nostartfiles. Uzywanie _init i _fini jest obecnie przestarzale, na korzysc opisanych wczesniej konstruktorow i destruktorow, ktore oprocz innych zalet, pozwalaja na zdefiniowanie wielu funkcji inicjalizacji i konczenia. Od glibc 2.2.3, atexit(3) moze posluzyc do zarejestrowania uchwytu wyjscia, ktory jest automatycznie wywolywany przez odlaczaniu obiektu dzielonego. Historia Funkcje te sa czescia API dlopen, wywodzacego sie z SunOS. USTERKI Wedlug stanu na glibc 2.24, podanie znacznika RTLD_GLOBAL przy wywolywaniu dlmopen() generuje blad. Co wiecej, podanie RTLD_GLOBAL przy wywolywaniu dlopen() powoduje zalamanie programu (SIGSEGV), jesli wywolanie nastapilo z dowolnego obiektu zaladowanego w przestrzeni nazw innej niz pierwotna przestrzen nazw. PRZYKLADY Ponizszy program laduje biblioteke matematyczna (glibc), szuka adresu funkcji cos(3) i wypisuje cosinus liczby 2.0. Pokazano przyklad tworzenia i dzialania programu: $ cc dlopen_demo.c -ldl $ ./a.out -0.416147 Kod zrodlowy programu #include #include #include #include /* Definiuje LIBM_SO (bedzie to lancuch podobny do "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(); /* Czysci ewentualne istniejace bledy */ cosine = (double (*)(double)) dlsym(handle, "cos"); /* Zgodnie ze standardem ISO C, rzutowanie pomiedzy wskaznikami funkcji a 'void *', jak ponizej, daje niezdefiniowany wynik. POSIX.1-2001 i POSIX.1-2008 akceptuja ten stan rzeczy i proponuja nastepujace obejscie problemu: *(void **) (&cosine) = dlsym(handle, "cos"); To (niezgrabne) rzutowanie jest zgodne ze standem ISO C i uniknie ostrzezen kompilatora. 2013 Technical Corrigendum 1 do POSIX.1-2008 poprawia stan rzeczy, wymagajac od zgodnych implementacji obslugi rzutowan 'void *' do wskaznika funkcji. Tym niemniej, niektore kompilatory (np. gcc z opcja '-pedantic') moga narzekac na rzutowanie uzyte 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 TAKZE 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 TLUMACZENIE Autorami polskiego tlumaczenia niniejszej strony podrecznika sa: Przemek Borys , Andrzej Krzysztofowicz i Michal Kulach Niniejsze tlumaczenie jest wolna dokumentacja. Blizsze informacje o warunkach licencji mozna uzyskac zapoznajac sie z GNU General Public License w wersji 3 lub nowszej. Nie przyjmuje sie ZADNEJ ODPOWIEDZIALNOSCI. Bledy w tlumaczeniu strony podrecznika prosimy zglaszac na adres listy dyskusyjnej . Linux man-pages 6.8 2 maja 2024 r. dlopen(3)