Subsections

13 LAB: Tworzenie bibliotek i metody linkowania

13.1 Ćwiczenie: Metody linkowania

W przypadku dołączania do kompilowanego programu funkcji z bibliotek istnieją dwie podstawowe metody linkowania programu z funkcjami bibliotecznymi:

UWAGA: Przed rozpoczęciem ćwiczeń należy:

Linkowanie statyczne polega na dołączeniu odpowiedniego kodu funkcji do programu na etapie kompilacji.

Linkowanie dynamiczne polega na dołączaniu do programu jedynie odpowiednich odwołań do funkcji bibliotecznych. Sam kod funkcji nie jest dołączany do programu na etapie kompilacji. Przy uruchomieniu programu, biblioteka jest natomiast ładowana do pamięci i udostępnia odpowiednie funkcje pracującemu programowi.

Podstawową zaletą linkowania dynamicznego jest to, że jest wymagany jeden egzemplarz biblioteki w pamięci dla wszystkich programów które z niej korzystają. Wadą jest to, że program linkowany dynamicznie jest zależny od dostępności zewnętrznym plików (bibliotek) i nie będzie działał poprawnie jeżeli ich nie znajdzie.

Aby możliwe było linkowanie dynamiczne biblioteka musi być specjalnie skonstruowana. Biblioteki dynamiczne pozwalają na linkowanie dynamiczne i statyczne (tak, jak bibliotek libC). Zdecydowana większość używanych bibliotek to biblioteki dynamiczne! Niezwykle rzadko zdarzają się biblioteki nadające się jedynie do linkowania statycznego.

13.2 Ćwiczenie: Linkowanie prostego programu

Proszę oglądnąć w edytorze zawartość programu power.c.

Proszę skompilować program i uruchomić:

$ gcc -Wall -ansi -pedantic power.c -o powerd -lm
$ ./powerd

Należy sprawdzić z jakimi bibliotekami jest linkowany, jaki jest jego rozmiar:

$ ldd powerd ; size powerd
Jak widać, domyślne jest linkowanie dynamiczne. Funkcje printf pow są dołączane z biblioteki standardowej, tj. libc.so.6.

Proszę skompilować program, wymuszając linkowanie statyczne i uruchomić:

$ gcc -static -Wall -ansi -pedantic power.c -o powers -lm
$ ./powers
Należy sprawdzić z jakimi bibliotekami jest linkowany, jaki jest jego rozmiar:
$ ldd powers ; size powers
Co się zmieniło? Który plik jest większy?

Porównać występujące w obu wersjach symbole (czyli nazwy obiektów takich jak np. funkcje):

$ nm powerd
$ nm powers
Dla uproszczenia można policzyć ich liczbę:
$ nm powerd|wc -l
$ nm powers|wc -l

13.3 Ćwiczenie: Biblioteki statyczne

Biblioteka statyczna jest prostym plikiem powstającym przez odpowiednie połączenie (,,sklejenie'') wynikowych plików binarnych (ang. objects) powstających po kompilacji (a przed linkowaniem!).

Celem ćwiczenia jest skonstruowanie prostej biblioteki statycznej, nazwanej as_zabawkis. Biblioteka będzie znajdować się w pliku as_zabawki.a. Dla programisty dostępny będzie plik nagłówkowy as_zabawkis.h pozwalający na korzystanie z biblioteki.

Biblioteka składa się z 2 modułów: i . Każdy z modułów składa się właściwego kodu (rozszerzenie .c) i nagłówka (rozszerzenie .h). Główny plik nagłówkowy biblioteki .c powstaje przez włącznie odpowiednich plików nagłówkowych modułów. Proszę obejrzeć wszystkie 5 plików!

W celu zbudowania biblioteki należy najpierw skompilować moduły:

$ gcc -c -Wall -ansi -pedantic as_licz.c -o as_licz.o
$ gcc -c -Wall -ansi -pedantic as_pisz.c -o as_pisz.o

a następnie połączyć je w plik biblioteki przy pomocy narzędzia ar (archiwizatora binarnego):

$ ar rcsv libas_zabawkis.a as_licz.o as_pisz.o

Przeczytać informacje o powstałym pliku:

$ file libas_zabawkis.a
$ nm libas_zabawkis.a
$ size libas_zabawkis.a

Tak przygotowana bibliotekę można użyć w programie as_test.c:

#include <stdio.h>
#include "as_zabawkis.h"

int main (void)
{
   int x;
   
   as_wypisz("Ala ma kota");
   x = as_dodaj(2,6);
   printf ("Wynik to: %d\n", x);
   
   return 0;
}

A następnie program skompilować:

$ gcc -c -Wall -ansi -pedantic as_test.c -o as_test.o

Plik wynikowy należy połączyć z biblioteką:

$ gcc -o as_static as_test.o -L. -las_zabawkis

Informacje o pliku:

$ ldd as_static

Program można przetestować:

$ ./as_static

Jak widać plik jest dynamicznie kompilowany z biblioteką standardową, a statycznie z własną as_zabawkis.

Aby kompilować program w pełni statycznie należy wywołać:

$ gcc -static -o as_static as_test.o -L. -las_zabawkis
$ ldd as_static

13.4 Ćwiczenie: Przygotowanie biblioteki dynamicznej

Aby skompilować kod do użycia jako bibliotekę dynamiczną należy go odpowiednio skompilować:

$ gcc -fPIC -c -Wall -ansi -pedantic as_licz.c -o as_licz.o
$ gcc -fPIC -c -Wall -ansi -pedantic as_pisz.c -o as_pisz.o
Najważniejsza jest opcja -fPIC powodująca utworzenia relokowalnego kodu (który może być ładowany w dowolny obszar pamięci) (ang. Position Independent Code).

Następnie należy go połączyć w bibliotekę:

$ gcc -shared -Wl,-soname,libas_zabawki.so.1 -o libas_zabawki.so.1.0.1 as_licz.o as_pisz.o
Najważniejsze są tu opcje:

Informacje o powstałej bibliotece:

$ file libas_zabawki.so.1.0.1
$ ldd libas_zabawki.so.1.0.1
Jak widać powstały plik dzielony jest dynamicznie zlinkowany z biblioteką standardową.

13.5 Ćwiczenie: Udostępnianie biblioteki dzielonej

Dynamiczne biblioteki dzielone powinny być dostępne również pod nazwą wskazującą główną wersją biblioteki i bibliotekę dostępną pod samą jej nazwą bez wersji. Jest to udostępniane przez dowiązania symboliczne.

W tym przypadku należy utworzyć dowiązania:

$ ln -s libas_zabawki.so.1.0.1 libas_zabawki.so.1
$ ln -s libas_zabawki.so.1.0.1 libas_zabawki.so

Za dynamiczne ładowanie biblioteki do pamięci w trakcie ładowania programu, odpowiedzialny jest linker dynamiczny. Standardowo w GNU/Linux to: ld-linux.so.

Biblioteki są wyszukiwane przez linker dynamiczny na podstawie jego głównej konfiguracji, która po uruchomieniu systemu jest przechowywana w pamięci. Cache linkera można oglądnąć przez polecenie:

$ /sbin/ldconfig -p
Własne katalogi z bibliotekami można dopisać (w GNU/Linux) do pliku /etc/ld.so.conf.

Na wyszukiwanie bibliotek przez linker można również doraźnie wpłynąć poprzez zmienną środowiskową. Tak należy zrobić w tym przypadku:

$ export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:`pwd`"
Uwaga na odwrotne apostrofy - te ponad klawiszem tabulacji.

13.6 Ćwiczenie: Kompilowanie z biblioteką dzieloną

Po takim przygotowaniu można skompilować program z nową biblioteką:

$ gcc -o as_dynamic as_test.o -L. -las_zabawki
$ file as_dynamic
$ ldd as_dynamic

Program można przetestować:

$ ./as_dynamic

13.7 Ćwiczenie: Przygotowanie dynamicznie ładowanej biblioteki dzielonej (DLL

Program może ładować w trakcie pracy potrzebne mu biblioteki. Linker dostarcza w tym celu 4 funkcji:

Funkcje te są udostępniane poprzez nagłówek dlfcn.h.

Pracę z dynamicznie ładowaną biblioteką należy prześledzić na nowym programie as_laduj.c.

Proszę się mu przyjrzeć przed kompilacją:

#include <stdio.h>

/* lokalne funkcje */
int as_dodaj (int a, int b) 
{
   int w;
   
   w = (a + b)*2;
   
   return w;
}

int as_wypisz (char *t) 
{
   printf("LOKALNE: %s \n", t);
   return 0;
}

#include <dlfcn.h>
void  *Biblioteka;
int   (*Funkcjav)();
int   (*Funkcja2i)(int,int);
float (*Funkcjaf)(float);
const char *blad;


int main (void)
{
   int x;
   int stan;
   double f;

   printf("\n");

   /* dzialanie niezalezne od biblioteki */
   as_wypisz("Ala ma kota");
   x = as_dodaj(2,6);
   printf ("Wynik 2+6 to: %d\n", x);

   Biblioteka = dlopen("libas_zabawki.so", RTLD_LAZY);
   blad = dlerror();
   printf("\n    Otwarcie biblioteki \"libas_zabawki\", rezultat -%s- \n", blad);
   if( blad ) return(1);

   Funkcjav = dlsym(Biblioteka, "as_wypisz");
   blad = dlerror();
   printf("    Szukanie funkcji \"as_wypisz\", rezultat -%s- \n", blad);
   if( blad ) return(1);

   stan = (*Funkcjav)("Ala ma kota");
   printf("    Uruchomienie funkcji \"as_wypisz\", rezultat -%s- \n", blad);

   Funkcja2i = dlsym(Biblioteka, "as_dodaj");
   blad = dlerror();
   printf("    Szukanie funkcji \"dodaj\", rezultat -%s- \n", blad);
   if( blad ) return(1);

   stan = (*Funkcja2i)(2,2);
   printf("    Uruchomienie funkcji \"as_dodaj\", wynik=\"%d\": rezultat: -%s- \n", stan, blad);

   stan = dlclose(Biblioteka);
   blad = dlerror();
   printf("    Zamykanie biblioteki \"libas_zabawki\", rezultat -%s-\n", blad); 
   if( stan ) return(1);

   /* a teraz bib mat */
   Biblioteka = dlopen("libm.so", RTLD_LAZY);
   blad = dlerror();
   if( blad ) return(1);
   Funkcjaf = dlsym(Biblioteka, "sinf");
   blad = dlerror();
   if( blad ) return(1);
   f = (*Funkcjaf)(3.1);
   printf("\n    Uruchomienie funkcji \"sinf\" (sinus), wynik=\"%f\", rezultat:-%s- \n", f, blad);
   stan = dlclose(Biblioteka);
   blad = dlerror();
   if( stan ) return(1);

   return 0;
}

Warto zwrócić uwagę, że program nie korzysta z zewnętrznych bibliotek (poza standardową). Można go więc skompilować bez dodatkowych opcji:

$ gcc -Wall -ansi -pedantic -o as_laduj as_laduj.c -ldl
$ ldd as_laduj

Opcja -ldl powoduje dołączenie odpowiedniego kodu linkera dynamicznego. Nie należy zwracać uwagę na ew. ostrzeżenie.

Proszę przetestować program i zwrócić uwagę na wyniki:

$ ./as_laduj

Następnie proszę usunąć własną bibliotekę i zobaczyć efekt:

$ rm libas_zabawki.so
$ ./as_laduj

13.8 BIBLIOGRAFIA



Grzegorz J. Nalepa 2007-10-22