   #Start Contents

          Pisanie wlasnych bibliotek w jezyku asembler pod Linuksem

   Pewnie zdarzylo sie juz wam uslyszec o kims innym:
   "Alez on(a) jest swietnym(a) programista(ka)! Nawet pisze wlasne
   biblioteki!"
   Pokaze teraz, ze nie jest to trudne, nie wazne jak przerazajacym sie
   to moze wydawac. Osoby, ktore przeczytaja ten artykul i zdobeda
   troszke wprawy beda mogly mowic:
   "Phi, a co to za filozofia pisac wlasne biblioteki!"

   Zacznijmy wiec od pytania: co powinno sie znalezc w takiej bibliotece?
   Moga to byc:
     * Funkcje wejscia i wyjscia, podobnie jak na przyklad w jezyku C
     * Funkcje, ktore juz przepisywalismy ze 20 razy w roznych programach
     * Sprawdzone funkcje, napisane przez kogos innego, ktorych nie
       umielibysmy sami napisac, lub ktore po prostu moga sie przydac

   Co to zas jest to owa biblioteka?
   Jest to plik na ktory sklada sie skompilowany kod, a wiec na przyklad
   pliki .o. Sama biblioteka najczesciej ma rozszerzenie .a (gdy zawiera
   statyczny kod) lub .so.* (dla bibliotek wspoldzielonych). Biblioteka
   eksportuje na zewnatrz nazwy procedur w niej zawartych, aby linker
   wiedzial, jaki adres podac programowi, ktory chce skorzystac z takiej
   procedury.

   Bede w tym artykule uzywal skladni i linii polecen NASMa (Netwide
   Assembler) z linkerem LD i archiwizatorem AR.

   Napiszmy wiec jakis prosty kod zrodlowy. Oto on:
   (przeskocz przykladowy modul biblioteki)
;  Biblioteka Standardowa
;  Emisja dzwieku przez glosniczek
;  Autor: Bogdan Drozdowski, 09.2002
;  kontakt: bogdandr MALPKA op.pl
;  Wersja Linux: 05.02.2004
;  Ostatnia modyfikacja: 29.08.2004

%include "../incl/linuxbsd/nasm/n_system.inc"

global  _graj_dzwiek

KIOCSOUND       equ     0x4B2F

section .data

konsola         db      "/dev/console", 0

struc timespec
        .tv_sec:        resd 1
        .tv_nsec:       resd 1
endstruc

t1 istruc timespec
t2 istruc timespec

segment biblioteka_dzwiek


_graj_dzwiek:

;  graj

;  wejscie:     BX = zadana czestotliwosc dzwieku w Hz, co najmniej 19
;               CX:DX = czas trwania dzwieku w mikrosekundach
;
;  wyjscie:     CF = 0 - wykonane bez bledow
;               CF = 1 - blad:   BX za maly

        pushfd
        push    eax
        push    ebx
        push    ecx
        push    edx
        push    esi

        cmp     bx, 19          ;najnizsza mozliwa czestotliwosc to ok. 18Hz
        jb      ._graj_blad

        push    ecx
        push    edx
        push    ebx

        mov     eax, sys_open   ; otwieramy konsole do zapisu
        mov     ebx, konsola
        mov     ecx, O_WRONLY
        mov     edx, 600q
        int     80h

        cmp     eax, 0
        jg      .otw_ok

        mov     eax, 1          ; jak nie otworzylismy konsoli,
                                ; piszemy na standardowe wyjscie
.otw_ok:
        mov     ebx, eax        ; EBX = uchwyt do pliku
        mov     esi, eax        ; ESI = uchwyt do pliku

        mov     eax, sys_ioctl  ; sys_ioctl = 54
        mov     ecx, KIOCSOUND
        xor     edx, edx        ; wylaczenie ewentualnych dzwiekow
        int     80h

        pop     ebx             ; BX = czestotliwosc
        mov     eax, 1234ddh
        xor     edx, edx
        div     ebx             ; EAX=1234DD/EBX - ta liczba idzie do ioctl

        mov     edx, eax
        mov     ebx, esi        ; EBX = uchwyt do konsoli lub stdout
        mov     eax, sys_ioctl
        int     80h

        pop     edx
        pop     ecx

                                ; pauza o dlugosci CX:DX mikrosekund:
        mov     eax, ecx
        shl     eax, 16
        mov     ebx, 1000000
        mov     ax, dx          ; EAX = CX:DX
        xor     edx, edx
        div     ebx
        mov     [t1+timespec.tv_sec], eax ;  EAX = liczba sekund

        mov     ebx, 1000
        mov     eax, edx
        mul     ebx
        mov     [t1+timespec.tv_nsec], eax ;  EAX = liczba nanosekund

        mov     eax, sys_nanosleep
        mov     ebx, t1
        mov     ecx, t2
        int     80h             ; robimy przerwe...


        mov     eax, sys_ioctl
        mov     ebx, esi        ; EBX = uchwyt do konsoli/stdout
        mov     ecx, KIOCSOUND
        xor     edx, edx        ; wylaczamy dzwiek
        int     80h

        cmp     ebx, 2          ; nie zamykamy stdout
        jbe     ._graj_koniec

        mov     eax, sys_close  ; sys_close = 6
        int     80h


._graj_koniec:
        pop     esi
        pop     edx
        pop     ecx
        pop     ebx
        pop     eax
        popfd
        clc                     ;  zwroc brak bledu

        ret


._graj_blad:
        pop     esi
        pop     edx
        pop     ecx
        pop     ebx
        pop     eax

        popfd
        stc                     ;  zwroc blad

        ret


   Jest to moja procedura wytwarzajaca dzwiek w glosniczku (patrz moj
   artykul o programowaniu glosniczka). Troche tego jest, co? Ale jest tu
   duzo spraw, ktore mozna omowic.

   Zacznijmy wiec po kolei:
    1. global...
       Funkcje, ktore maja byc widoczne na zewnatrz tego pliku, a wiec
       mozliwe do uzycia przez inne programy, musza byc zadeklarowane
       jako "public" (w NASMie: global). Tutaj jest to na wszelki
       wypadek. Niektore kompilatory domyslnie traktuja wszystkie symbole
       jako publiczne, inne nie. Jesli w programie bedziemy chcieli
       korzystac z takiej funkcji, nalezy ja zadeklarowac jako "extrn"
       (FASM) lub "extern" (NASM).
    2. Deklaracja segmentu
       Zaden przyzwoity kompilator nie pozwoli na pisanie kodu poza
       jakimkolwiek segmentem (no chyba, ze domyslnie zaklada segment
       kodu, jak NASM). Normalnie, w zwyklych programach, role te pelni
       dyrektywa "section .text".
    3. Gwiazdki lub inne elementy oddzielajace (tu usuniete)
       Moga sie wydawac smieszne lub niepotrzebne, ale gdy liczba
       procedur w pliku zaczyna siegac 10-20, to NAPRAWDE zwiekszaja
       czytelnosc kodu, oddzielajac procedury, dane itd.
    4. Deklaracja procedury (wczesniej zadeklarowanej jako global)
       Znak podkreslenia z przodu jest tylko po to, by w razie czego nie
       byl identyczny z jakas etykieta w programie korzystajacym z
       biblioteki.
    5. To, czego procedura oczekuje i to, co zwraca.
       Jedna procedure latwo zapamietac. Ale co zrobic, gdy jest ich juz
       100? Analizowac kod kazdej, aby sprawdzic, co robi, bo akurat
       szukamy takiej jednej....? No przeciez nie.
    6. Dobra technika programowania jest deklaracja stalych w stylu EQU
       (lub #define w C). Zamiast nic nie znaczacej liczby mozna uzyc
       wiele znaczacego zwrotu, co przyda sie dalej w kodzie. I nie
       kosztuje to ani jednego bajtu. Oczywiscie, ukrywa to czesc kodu
       (tutaj: numery portow), ale w razie potrzeby zmienia sie te
       wielkosc tylko w 1 miejscu, a nie w 20.
    7. Zachowywanie zmienianych rejestrow (push)
       Poza wartosciami zwracanymi nic nie moze byc zmienione!
       Nieprzyjemnym uczuciem byloby spedzenie kilku godzin przy
       odpluskwianiu (debugowaniu) programu tylko dlatego, ze ktos
       zapomnial zachowac jakiegos rejestru, prawda?
    8. Sprawdzanie warunkow wejscia, czy sa prawidlowe. Zawsze nalezy
       wszystko przewidziec.
    9. Kod procedury. Z punktu widzenia tego artykulu jego tresc jest dla
       nas nieistotna.
   10. Punkt(y) wyjscia
       Procedura moze miec dowolnie wiele punktow wyjscia. Tutaj
       zastosowano dwa, dla dwoch roznych sytuacji:
         1. parametr byl dobry, procedura zakonczyla sie bez bledow
         2. parametr byl zly, zwroc informacje o bledzie

   Mamy wiec juz plik zrodlowy. Co z nim zrobic? Skompilowac, oczywiscie!
nasm -f elf naszplik.asm

   (-f - okresl format pliku wyjsciowego: Executable-Linkable Format,
   typowy dla Linuksa)

   Mamy juz plik naszplik.o. W pewnym sensie on juz jest biblioteka! I
   mozna go uzywac w innych programach, na przyklad w pliku program2.asm
   mamy (FASM):
        ...
        extrn _graj_dzwiek              ; NASM: extern _graj_dzwiek

        ...
        ...
        mov bx,440
        mov cx,0fh
        mov dx,4240h
        call _graj_dzwiek
        ...

   I mozemy teraz zrobic:
        nasm -f elf program2.asm
        ld -s -o program2 program2.o naszplik.o

   a linker zajmie sie wszystkim za nas - utworzy plik program2,
   zawierajacy takze naszplik.o. Jaka z tego korzysc? Plik program2.asm
   moze bedzie zmieniany w przyszlosci wiele razy, ale naszplik.asm/.o
   bedzie ciagle taki sam. A w razie checi zmiany procedury _graj_dzwiek
   wystarczy ja zmienic w jednym pliku i tylko jego ponownie skompilowac,
   bez potrzeby wprowadzania tej samej zmiany w kilkunastu innych
   programach. Te programy wystarczy tylko ponownie skompilowac z nowa
   "biblioteka", bez jakichkolwiek zmian kodu.

   No dobra, ale co z plikami .a?
   Otoz sa one odpowiednio polaczonymi plikami .o. I wszystko dziala tak
   samo.

   No ale jak to zrobic?
   Sluza do tego specjalne programy, w DOSie nazywane "librarian"
   (bibliotekarz). My tutaj uzyjemy archiwizatora AR. Pliki .o, ktore
   chcemy polaczyc w biblioteke podajemy na linii polecen:
        ar -r libasm.a plik1.o plik2.o

   I otrzymujemy plik libasm.a, ktory mozna dolaczac linkerem do
   programow:
        ld -s -o naszprog naszprog.o -L/sciezka_do_pliku.a -lasm

   lub:
        ld -s -o naszprog naszprog.o /sciezka_do_pliku.a/libasm.a
     _________________________________________________________________

Biblioteki wspoldzielone .so

   Prawie wszystkie programy w Linuksie uzywaja podstawowej biblioteki
   systemu - biblioteki jezyka C. Wyobrazacie sobie, ile miejsca w
   pamieci zajelyby wszystkie uzywane kopie tej biblioteki? Na pewno
   niemalo. A poradzono sobie z tym, tworzac specjalny rodzaj plikow -
   biblioteke wspoldzielona, ladowana i laczona z programem dynamicznie
   (w chwili uruchomienia). Pliki te (o rozszerzeniu .so) sa
   odpowiednikami plikow DLL znanych z systemow Windows. Teraz pokaze,
   jak pisac i kompilowac takie pliki. Wszystko to znajdziecie tez w
   dokumentacji kompilatora NASM.

   Reguly sa takie:
    1. Dalej trzymajcie sie wszystkich powyzszych uwag do kodu
       (komentarze itp.).
    2. NIE mozemy juz sie odwolywac normalnie do swoich wlasnych
       zmiennych!
       Dlaczego? Przyczyna jest prosta: biblioteki wspoldzielone sa
       pisane jako kod niezalezny od pozycji (Position-Independent Code,
       PIC) i po prostu nie wiedza, pod jakim adresem zostana zaladowane
       przez system. Adres moze za kazdym razem byc inny. Do swoich
       zmiennych musimy sie wiec odwolywac troche inaczej, niz to bylo do
       tej pory. Do biblioteki wspoldzielonej linker dolacza strukture
       Globalnej Tablicy Offsetow (Global Offset Table, GOT). Biblioteka
       deklaruje ja jako zewnetrzna i korzysta z niej do ustalenia adresu
       swojego kodu. Wystarczy wykonac call zaraz / zaraz: pop ebx i juz
       adres etykiety "zaraz" znajduje sie w EBX. Dodajemy do niego adres
       GOT od poczatku sekcji (_GLOBAL_OFFSET_TABLE_ wrt ..gotpc) i adres
       poczatku sekcji, otrzymujac realny adres tablicy GOT + adres
       etykiety "zaraz". Potem juz tylko wystarczy odjac adres etykiety
       "zaraz" i juz EBX zawiera adres GOT. Do zmiennych mozemy sie teraz
       odnosic poprzez [ebx+nazwa_zmiennej].
    3. Kompilacja i laczenie.
       O ile kompilacja NASMem jest taka, jak zawsze, to laczenie
       programu jest zdecydowanie inne. Popatrzcie na opcje LD:
          + -shared
            Mowi o tym, ze LD ma zbudowac biblioteke wspoldzielona,
            zamiast zwyczajnego pliku wykonywalnego. LD zadba o wszystko,
            co trzeba (GOT itd).
          + -soname biblso.so.1
            Nazwa biblioteki. Ale uwaga - NIE jest to nazwa pliku, tylko
            wewnetrzna nazwa samej biblioteki. Jak bedziecie dodawac
            kolejne wersje, to nie zmieniajcie nazwy wewnetrznej, tylko
            nazwe pliku .so, a zrobcie dowiazanie symboliczne do tego
            pliku, z nazwa taka jak wewnetrzna nazwa biblioteki, na
            przyklad waszabibl.so.1 jako link do waszabibl.so.1.1.5.
    4. Deklaracje zmiennych i funkcji globalnych.
       Kazda funkcja, ktora chcemy zrobic globalna (widoczna dla
       programow korzystajacych z biblioteki), musi byc zadeklarowana nie
       tylko jako extern, ale musimy podac tez, ze jest to funkcja. Pelna
       dyrektywa wyglada teraz:
                global nazwaFunkcji:function
       Przy eksportowaniu danych dodajemy slowo "data" i rozmiar danych,
       na przyklad dla tablic:

        global tablica1:data tablica1_dlugosc

        tablica1:               resb    100
        tablica1_dlugosc        equ     $ - tablica1
    5. Uruchamianie funkcji zewnetrznych (na przyklad z biblioteki C)
       Sprawa jest juz duzo prostsza niz w przypadku danych. Funkcje
       zewnetrzna deklarujemy oczywiscie slowem "extern", a zamiast "call
       nazwaFunkcji" piszemy
                call nazwaFunkcji wrt ..plt
       PLT oznacza Procedure Linkage Table, czyli tablice linkowania
       procedur (funkcji). Zawiera ona skoki do odpowiednich miejsc,
       gdzie znajduje sie dana funkcja.

   A oto gotowy przyklad. Biblioteka eksportuje jedna funkcje, ktora po
   prostu wyswietla napis.
   (przeskocz przykladowa biblioteke wspoldzielona)
; Przyklad linuksowej biblioteki wspoldzielonej .so
;
; Autor: Bogdan D., bogdandr (at) op.pl
;
; kompilacja:
;   nasm -f elf -o biblso.o biblso.asm
;   ld -shared -soname biblso.so.1 -o biblso.so.1 biblso.o


section .text
extern  _GLOBAL_OFFSET_TABLE_   ; zewnetrzny, uzupelniony przez linker
global  info:function           ; eksportowana funkcja
;extern printf                  ; funkcja zewnetrzna

; makro do pobierania adresu GOT; wynik w EBX.
%imacro wez_GOT 0

        call    %%zaraz
        %%zaraz:
        pop     ebx
        add     ebx, _GLOBAL_OFFSET_TABLE_ + $$ - %%zaraz wrt ..gotpc
%endmacro

info:
                                ; zachowanie zmienianych rejestrow
        push    eax
        push    ebx
        push    ecx
        push    edx

        wez_GOT                 ; pobieramy adres GOT
        push    ebx             ; zachowujemy EBX

        mov     eax, 4          ; funkcja pisania do pliku
                        ; do ECX zaladuj ADRES napisu (stad LEA a nie MOV)
        lea     ecx, [ebx + napis wrt ..gotoff]
        mov     ebx, 1          ; plik = 1 = standardowe wyjscie (ekran)
        mov     edx, napis_dl   ; dlugosc napisu
        int     80h             ; wyswietl

; a tak uruchamiamy funkcje zewnetrzne:
        pop     ebx             ; przywracamy EBX
        lea     ecx, [ebx + napis wrt ..gotoff] ; ECX = adres napisu
        push    ecx             ; adres na stos (jak dla funkcji z C)
;       call    printf wrt ..plt        ; uruchomienie funkcji
        add     esp, 4          ; usuniecie argumentow ze stosu

                                ; przywracanie rejestrow
        pop     edx
        pop     ecx
        pop     ebx
        pop     eax

        xor     eax, eax        ; funkcja zwraca 0 jako brak bledu
        ret

section .data

napis           db      "Jestem biblioteka wspoldzielona!", 10, 0
napis_dl        equ     $ - napis

   Program sprawdzajacy, czy biblioteka dziala jest wyjatkowo prosty:
   jedno uruchomienie funkcji z biblioteki i wyjscie. Na uwage zasluguje
   jednak ta dluga linijka z uruchomieniem LD. Przyjrzyjmy sie blizej:
     * -dynamic-linker /lib/ld-linux.so.2
       Mowi o nazwie programu, ktorego trzeba uzyc do dynamicznego
       laczenia. Bez tej opcji nasz program nie podziala i dostaniemy
       blad "Accessing a corrupted shared library"
     * -nostdlib
       Nie dolacza zadnych standardowych bibliotek.
     * -o biblsotest biblsotest.o
       Nazwy pliku wyjsciowego i wejsciowego.
     * biblso.so.1
       Biblioteka, z ktora nalezy polaczyc ten program

   (przeskocz test biblioteki wspoldzielonej)
; Program testujacy linuksowa biblioteke wspoldzielona .so
;
; Autor: Bogdan D., bogdandr (at) op.pl
;
; kompilacja:
; nasm -f elf -o biblsotest.o biblsotest.asm
; ld -dynamic-linker /lib/ld-linux.so.2 -nostdlib \
;       -o biblsotest biblsotest.o biblso.so.1


section .text
global  _start

extern  info

_start:

        call    info

        mov     eax, 1
        xor     ebx, ebx
        int     80h

   Jesli dostajecie blad "/usr/lib/libc.so.1: bad ELF interpreter: No
   such file or directory", to utworzcie w katalogu /usr/lib (jako root)
   plik libc.so.1 jako dowiazanie symboliczne do libc.so i upewnijcie
   sie, ze plik /usr/lib/libc.so ma prawa wykonywania dla wszystkich.

   Jesli system nie widzi biblioteki wspoldzielonej (a nie chcecie jej
   pakowac do globalnych katalogow jak /lib czy /usr/lib), nalezy ustawic
   dodatkowa sciezke ich poszukiwania.
   Ustawcie sobie zmienna srodowiskowa LD_LIBRARY_PATH tak, by zawierala
   sciezki do Waszych bibliotek. Ja u siebie mam ustawiona
   LD_LIBRARY_PATH=$HOME:. , co oznacza, ze poza domyslnymi katalogami,
   ma byc przeszukany takze moj katalog domowy oraz katalog biezacy (ta
   kropka po dwukropku), jakikolwiek by nie byl.
     _________________________________________________________________

Ladowanie bibliotek w czasie pracy programu

   Gdy nasz program jest na sztywno (statycznie lub nie) laczony z jakas
   biblioteka wspoldzielona, to w trakcie jego uruchamiania system szuka
   pliku tej biblioteki, aby moc uruchomic nasz program. Jesli system nie
   znajdzie biblioteki, to nawet nie uruchomi naszego programu. Czasem
   jednak chcemy miec mozliwosc zareagowania na taki problem. Oczywiscie,
   bez kluczowych bibliotek nie ma szans uruchomic programu, ale cala
   reszte mozna dosc latwo ladowac w czasie dzialania programu. Daje to
   pewne korzysci:
    1. oszczedza pamiec - ladujemy tylko te biblioteki, ktorych nam
       naprawde potrzeba, a tuz po zakonczeniu pracy z biblioteka, mozna
       zwolnic pamiec przez nia zajmowana.
    2. daje mozliwosc reagowania na brak biblioteki - na przyklad mozna
       wyswietlic komunikat, ze niektore funkcje programu beda
       niedostepne. Ale program moze nadal dzialac i wykonac swoje
       zadanie.

   Ladowanie bibliotek w czasie pracy programu polega na wykorzystaniu
   funkcji z biblioteki libdl. Konkretnie, uzyjemy trzech funkcji:
    1. dlopen - otwiera i laduje biblioteke
       Przyjmuje ona dwa argumenty. Od lewej (ostatni wkladany na stos)
       sa to: nazwa pliku biblioteki wspoldzielonej (razem ze sciezka,
       jesli jest w niestandardowej) oraz jedna z liczb: RTLD_LAZY
       (wartosc 1), RTLD_NOW (wartosc 2), RTLD_GLOBAL (wartosc 100h).
       Okreslaja one sposob dostepu do funkcji w bibliotece, odpowiednio
       sa to:
          + RTLD_LAZY - znajduj adres funkcji w chwili jej wywolania.
          + RTLD_NOW - znajduj adres funkcji od razu, w czasie ladowania
            biblioteki
          + RTLD_GLOBAL - symbole biblioteki (nazwy funkcji) beda od razu
            widoczne dla programu tak, jakby biblioteka byla wlaczona na
            stale do programu.
       Funkcja dlopen zwraca (w EAX) adres zaladowanej biblioteki,
       ktorego bedziemy potem uzywac.
    2. dlsym - wylawia z biblioteki adres interesujacej nas funkcji
       Ta funkcja tez przyjmuje dwa argumenty. Od lewej (ostatni wkladany
       na stos) sa to: adres biblioteki, ktory otrzymalismy od funkcji
       dlopen oraz nazwa funkcji, ktora nas interesuje jako lancuch
       znakow.
       Funkcja dlsym zwraca nam (w EAX) adres zadanej funkcji.
    3. dlclose - zamyka zaladowana biblioteke
       Jedynym argumentem tej funkcji jest adres biblioteki, ktory
       otrzymalismy od funkcji dlopen.

   Jest tez funkcja systemowa sys_uselib, ale jej dokumentacja jest
   skromna. W uzyciu pewnie bylaby trudniejsza niz libdl.

   Pora na przykladowy program. Jego zadaniem bedzie zaladowac biblioteke
   biblso.so.1, ktora utworzylismy w poprzednim podrozdziale, oraz
   uruchomienie jej jedynej funkcji - info. Oto kod w skladnie NASM:
   (przeskocz program ladujacy biblioteke)
; Program korzystajacy z biblioteki wspoldzielonej tak, ze
;       nie musi byc z nia laczony
;
; Autor: Bogdan D., bogdandr (na) op . pl
;
; kompilacja:
;   nasm -f elf -o shartest.o shartest.asm
;   gcc -s -o shartest shartest.o -ldl

section .text

; bedziemy korzystac z biblioteki jezyka C, wiec nasza funkcja
;  glowna musi sie nazywac  "main"
global main

%define RTLD_LAZY       0x00001 ; znajduj adres funkcji w chwili wywolania
%define RTLD_NOW        0x00002 ; znajduj adres funkcji od razu, w czasie
                                ; ladowania biblioteki
%define RTLD_GLOBAL     0x00100 ; czy symbole beda od razu widoczne

extern dlopen
extern dlsym
extern dlclose

main:

        push    dword RTLD_LAZY ; ladowanie na zadanie
        push    dword bibl      ; adres nazwy pliku
        call    dlopen          ; otwieramy biblioteke
        add     esp, 2*4        ; zwalniamy argumenty ze stosu

        test    eax, eax        ; sprawdzamy, czy nie blad (EAX=0)
        jz      .koniec

        mov     [uchwyt], eax   ; zachowujemy adres biblioteki

        push    dword funkcja   ; adres nazwy zadanej funkcji
        push    dword [uchwyt]  ; adres biblioteki
        call    dlsym           ; szukamy adresu
        add     esp, 2*4

        mov     [adr_fun], eax  ; EAX = znaleziony adres
        call    eax             ; uruchomienie bezposrednie
        call    [adr_fun]       ; uruchomienie posrednie

        push    dword [uchwyt]  ; adres biblioteki
        call    dlclose         ; zwalniamy ja z pamieci
        add     esp, 1*4

.koniec:
        ret                     ; zakonczenie funkcji main

section .data

bibl            db      "biblso.so.1", 0        ; nazwa biblioteki
funkcja         db      "info", 0               ; nazwa szukanej funkcji
uchwyt          dd      0
adr_fun         dd      0

   Musze wspomniec o dwoch dosc waznych rzeczach.
   Pierwsza jest sposob kompilacji. Skoro laczymy nasz program z
   biblioteka C, to nasza funkcja glowna musi sie teraz nazywac main, a
   NIE _start (gdyz funkcja _start juz jest w bibliotece jezyka C).
   Kompilacja wyglada teraz tak, jak napisalem w programie:
nasm -f elf -o shartest.o shartest.asm
gcc -s -o shartest shartest.o -ldl

   W tym przypadku kompilator GCC uruchamia za nas linker LD, ktory
   dolaczy niezbedne biblioteki.

   Druga rzecza jest domyslna sciezka poszukiwania bibliotek
   wspoldzielonych. Jesli nie chcecie zasmiecac systemu (lub nie macie
   uprawnien), pakujac swoje biblioteki do /lib czy /usr/lib, ustawcie
   sobie zmienna srodowiskowa LD_LIBRARY_PATH tak, by zawierala sciezki
   do Waszych bibliotek. Ja u siebie mam ustawiona
   LD_LIBRARY_PATH=$HOME:. , co oznacza, ze poza domyslnymi katalogami,
   ma byc przeszukany takze moj katalog domowy oraz katalog biezacy (ta
   kropka po dwukropku), jakikolwiek by nie byl.

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
