Introduktion till libfiler



Introduktion till hur man bygger statiska och dynamiska libfiler

Introduktion till hur man bygger statiska och dynamiska libfiler



Kort introduktion till libfiler
Observera att beskrivningen nedan kan skilja sig något åt mellan olika
Unixdialekter. Beskrivningen nedan ska dock fungera i GNU/Linux.

Biblioteksfiler finns i två former, statiska och dynamiska
(dynamiska kallas även delade/shared).

För statiska bibliotek så bakas delar av biblioteket in i programmet och ligger fast i
programmet. Om det finns ett statiskt bibliotek som heter m och
programmen foo och bar behöver m då ser man till att baka in m (delar av m) i
både foo och bar. Det gör att programfilerna blir större men de
är samtidigt oberoende av biblioteksfilen med biblioteket m i för att
kunna köra. Det är dock bara de funktioner/symboler i biblioteket som
ett programmen behöver som kommer att länkas med i programfilen.

För att installera och köra de statiskt länkade programmen foo eller bar
på ett system så behövs bara programfilerna foo respektive bar.
Biblioteket m behövs inte installeras
eftersom de nödvändiga delarna av den finns inbakad i binärerna.
Statiska bibliotek behövs bara när man länkar ihop objektfiler och bibliotek
till slutgiltigt program. Dessa bibliotek behövs inte på målsystemet.

Om det statiska biblioteket math (libmath.a) innehåller funktionerna sin och cos
där dessa ligger i separata objektfiler, t.e.x. sin.o och cos.o,
och programmet foo är beroende av sin
då kommer programmet foo att endast länka med sin (sin.o). Om programmet
bar är beroende av cos så kommer programmet bar att endast länka med cos.
Programmen länkar alltså endast med de objektfiler för de funktioner
i de statiska biblioteket som de behöver.
Ett statiskt bibliotek är egentligen ett arkiv med objektfiler, där varje
objektfil kan innehålla en eller flera funktioner och symboler.
Om ett program behöver en funktion av flera i en objektfil då kommer
hela den objektfilen från det statiska biblioteket att länkas med.

Statiska biblioteksfiler heter typisk libmath.a där math är namnet
på biblioteket.



Dynamiska/delade bibliotek bakas inte in i programmen.
Programfilerna blir därmed mindre än med statiska bibliotek men
de blir beroende av biblioteksfiler. Om programmen foo och bar
behöver biblioteket m så kommer systemet att ladda in programfilen
och biblioteksfilen libm.so.6 in i minnet. Finessen är att biblioteksfilen
endast behövs laddas in en gång. Programmen foo och bar delar på
samma biblioteksfil.

Man ska dock vara medveten om att hela biblioteksfilen
laddas in i minnet så om biblioteksfilen innehåller många funktioner
kommer alla funktioner att laddas in även om det bara är några få som
används.

Biblioteksfilerna laddas in dynamiskt när de behövs och de delas
mellan flera program. Därav dynamiska delade bibliotek (shared libraries).
Dynamiska biblioteksfiler har typiskt namn på formen libfoo.so.x.y
eller libfoo.so.x.y.z där x är version (huvudversion) och y eller y.z är underversion och patch-release.
Filerna kan även ha namn på formen libfoo-x.y.so.
libzvt.so.2.2.10 är ett exempel på den första formen och
libutil-2.4.so är ett exempel på den senare formen.

Om man vid kompilering av ett program med gcc/g++ vill länka med ett
bibliotek så görs detta med flaggan -l
(lilla L). Det gäller både för statiska och dynamiska bibliotek. Om
ett bibliotek finns både som statiskt och dynamiskt kommer i första
hand det dynamiska att användas.

Exempel för att länka med mattefunktioner som finns i biblioteket m:
Kod: Markera allt
gcc program.c -lm -o programfil


biblioteksnamn
Namnet på ett statiskt bibliotek är det som stå mellan lib och
.a. I exemplet med libfoo.a blir det foo.
Namnet på ett dynamiskt bibliotek är det som står efter lib med .so
och huvudversions, underversion och patchrelease borttaget. Om biblioteksfilen heter
libfoo.so.1.2 eller libfoo-1.2.so så är namnet på biblioteket foo.

Länkning
Om man vill att länkaren ska söka i en viss katalog så anges detta med
-Lsökväg. T.ex. -L. eller -L/usr/local/foo/lib .


Det går även att ge sökvägen plus filnamn till ett bibliotek till
länkaren. exempel:
Kod: Markera allt
gcc program.c /usr/lib/libm.a -o programfil


Man kan tvinga länkaren att bygga en statiskt länkad binär med -static. Som standard länkas program dynamiskt.
Kod: Markera allt
gcc foo.c -Wall -static -o foo



Skapa statiska bibliotek
Statiska bibliotek har namn på formen libfoo.a .
För att skapa ett statiskt bibliotek gör man enligt följande:

Kompilera källkodsfilerna med flaggorna -g och -c,
Kod: Markera allt
gcc -g -c fil1.c fil2.c fil3.c
Om du även lägger till flaggan -fPIC till kommandoraden ovan kan du använda objektfilerna till både ett statiskt bibliotek och ett dynamiskt/delat.

Skapa biblioteksfilen med hjälp av programmet ar.
Kod: Markera allt
ar crv libfoo.a fil1.o fil2.o fil3.o


Skapa därefter en innehållsförteckning med ranlib. Detta steg är
nödvändigt i en del system men inte i Linux men det skadar inte att
göra det.
Kod: Markera allt
ranlib libfoo.a


Nu kan man lista vilka objektfiler som ingår i libfoo.a med:
Kod: Markera allt
ar t libfoo.a


För att lista alla globala symboler i biblioteket kan man använda nm. Då får man bland annat se alla globala funktioner som finns i biblioteket.
Kod: Markera allt
nm -g libfoo.a



Dynamiska/delade bibliotek
Dynamiska bibliotek benämns också som "shared libraries".

Huvudversion, underversion och patchrelease
När man gör större uppdateringar av ett bibliotek, där ändringarna inte är bakåtkompatibla, då ser man till att uppdatera huvudversionen. Det kan t.ex. vara att ta bort funktionalitet, ändra på gränssnitt etc. Dessa ändringar gör att program länkade mot en äldre huvudversion av biblioteket inte kan använda en nyare huvudversion. För det krävs eventuella ändringar i programmet och omkompilering och omlänkning av programmet.

Under-versioner och patchreleaser däremot uppdaterar man vid mindre ändringar, t.ex. buggfixar och utökningar som t.ex. kompatibla tillägg av funktionalitet som inte påverkar den existerande funktionaliteten. Ett program som är länkat mot en huvudversion ska kunna använda nya underversioner och patchreleaser utan omkompilering eller omlänkning. Så om programmet är länkat mot libfoo.so.1 kan det fungera med libfoo.so.1.0, libfoo.so.1.1, libfoo.so.1.2 och så vidare.



Skapa dynamiska/delade bibliotek

Om man har källkodsfilen foo.c och vill skapa biblioteksfilen
libfoo.so.1 så kan det göras på följande sätt:

Kompilera källkoden med:
Kod: Markera allt
gcc -fPIC -Wall -ansi -g -c fil.c


Skapa biblioteksfilen libfoo.so.1.0 som ska innehålla libfoo.so.1. Det görs med följande kommandorad:
Kod: Markera allt
gcc -g -shared -Wl,--soname,libfoo.so.1 -o libfoo.so.1.0 fil.o -lc



Information om libfilen kan man få fram med objdump. Exempel:
Kod: Markera allt
objdump -p libfoo.so.1.0

Med det kan man se vad SONAME är satt till, vilket i exemplet ovan bör vara
libfoo.so.1 . När man länkar ett program vill man länka mot huvudversion. Då kan programmet fungera även mot underversioner (mindre uppdateringar) och patchreleaser av biblioteket.


Även här man lista alla globala symboler i biblioteket med nm. Då får man bland annat se alla globala funktioner som finns i biblioteket.
Kod: Markera allt
nm -g libfoo.so.1.0



Nu är det bara att lägga in biblioteksfilen i en katalog som hittas av
systemet eller lägga det i en katalog, t.ex. /usr/local/mittprogram/lib
och stoppa in sökvägen /usr/local/mittprogram/lib i /etc/ld.so.conf
och därefter köra ldconfig. Programmet ldconfig kommer att sätta upp
symlänken libfoo.so.1 som pekar på libfoo.so.1.0 . Det går även att
köra ldconfig endast för katalogen med biblioteksfilen.
Därefter kan det vara bra att skapa en symlänk libfoo.so som pekar på
libfoo.so.1 där libfoo.so.1 är en länk till biblioteksfilen libfoo.so.1.y (där y är en underversion). Den symboliska länken libfoo.so behövs endast vid länkning i samband med kompilering. Länken libfoo.so.1 -> libfoo.so.1.y används vid körning. Se bild nedan, där libfoo.so.1.2 är biblioteksfilen.



Här sätter vi upp länken libfoo.so.1 -> libfoo.so.1.2 med kommandot ldconfig och sedan länken libfoo.so -> libfoo.so.1 med kommandot ln.
Kod: Markera allt
cp libfoo.so.1.2 /usr/local/mittprogram/lib/
cd /usr/local/mittprogram/lib
/sbin/ldconfig -v -n .
ln -sf libfoo.so.1 libfoo.so



Program som länkas mot libfoo.so.1 kommer att kunna använda
libfoo.so.1.0 som innehåller libfoo.so.1. Om man vid ett senare
tillfälle gör mindre uppdateringar i källkoden och kompilerar om den
på samma sätt som ovan men till filen libfoo.so.1.1 så kommer
programmen att kunna använda den nya filen eftersom den innehåller
libfoo.so.1 som är det bibliotek som programmen förväntar sig att hitta.

Principen brukar vara att vid mindre uppdateringar så har man samma
soname, ovan libfoo.so.1, och uppdaterar underversionen på libfilerna.
Vid större inkompatibla ändringar ger man ett nytt soname med en
uppdaterad versionssiffra vilket med exemplet ovan skulle ge
libfoo.so.2 och filen libfoo.so.2.0 (libfoo.so.2.x).

Genom att göra på det här sättet så kan gamla program fortsätta att
använda libfoo.so.1 (i filerna libfoo.so.1.x) och nya program kan
använda libfoo.so.2 (i filerna libfoo.so.2.x).

Om du tänker använda libtools versionssystem bör du se infosidorna
till libtool, specifikt "Updating version info".
Kod: Markera allt
info libtool "Updating version info"



Vid start av ett program kommer /lib/ld-linux.so.2 att sköta om så
att de dynamiska libfiler laddas in som programmet behöver. Alla
beroenden slås upp och laddas in vilket gör att det kan ta tid att
starta ett program.

För att ta reda på vilka bibliotek ett program behöver för att
kunna köra kan man använda programmet ldd.

ldd programfil

Exempel:
Kod: Markera allt
[kjell-e@dumburk c]$ ldd a.out
        linux-gate.so.1 =>  (0xb7fbe000)
        libc.so.6 => /lib/libc.so.6 (0x009f0000)
        /lib/ld-linux.so.2 (0x009d3000)


Ett alternativ till ldd är att köra ld-linux.so.2 --list på programmet:
Kod: Markera allt
[kjell-e@dumburk c]$ld-linux.so.2 --list program
        linux-vdso.so.1 (0x00007ffdc88ba000)
        libselinux.so.1 => /lib/libselinux.so.1 (0x00007f4d6c835000)
        libc.so.6 => /lib/libc.so.6 (0x00007f4d6c60d000)
        libpcre2-8.so.0 => /lib/libpcre2-8.so.0 (0x00007f4d6c576000)
        /lib/ld-linux.so.2 (0x00007f4d6c89f000)


ld-linux.so.2 kan ha ett lite annat namn i ditt system. I t.ex. Ubuntu 22.04 heter den ld-linux-x86-64.so.2, i ett inbyggt system med ARM CPU kan den t.ex. heta ld-linux-armhf.so.3.




Ett litet exempel på ett enkelt bibliotek och ett litet program som
använder det.
Kod: Markera allt
[kjell-e@dumburk c]$ cat  mitt.c
int mitt(int a) {
    return a * a;
}

[kjell-e@dumburk c]$ cat  mittest.c
#include<stdio.h>


int main() {
  printf("%d\n",mitt(17));
  return 0;
}


[kjell-e@dumburk c]$ gcc -fPIC -Wall -ansi -g -c mitt.c

[kjell-e@dumburk c]$ gcc -g -shared -Wl,--soname,libmitt.so.1 -o libmitt.so.1.0 mitt.o -lc


[kjell-e@dumburk c]$ /sbin/ldconfig -v -n .
.:
        libmitt.so.1 -> libmitt.so.1.0 (changed)

[kjell-e@dumburk c]$ ln -sf libmitt.so.1 libmitt.so

[kjell-e@dumburk c]$ gcc mittest.c -L.  -lmitt -o mittest

[kjell-e@dumburk c]$ ./mittest
289




Därefter är det dags att installera mittest i /usr/local/bin och
libmitt i /usr/local/mitt/lib och se till att systemet hittar biblioteksfilerna.
Kod: Markera allt
[root@dumburk c]# mv mittest /usr/local/bin

[root@dumburk c]# echo "/usr/local/mitt/lib" >> /etc/ld.so.conf

[root@dumburk c]# cp libmitt.so.1.0 /usr/local/mitt/lib/

[root@dumburk c]# ldconfig

[root@dumburk c]# ls -l /usr/local/mitt/lib/
totalt 8
lrwxrwxrwx 1 root root   14  1 apr 00.00 libmitt.so.1 -> libmitt.so.1.0
-rwxr-xr-x 1 root root 5177 31 mar 23.59 libmitt.so.1.0

[root@dumburk c]# ln -sf /usr/local/mitt/lib/libmitt.so.1  /usr/local/mitt/lib/libmitt.so

[root@dumburk c]# ls -l /usr/local/mitt/lib/
totalt 8
lrwxrwxrwx 1 root root   34  1 apr 00.01 libmitt.so -> /usr/local/mitt/lib/libmitt.so.1
lrwxrwxrwx 1 root root   14  1 apr 00.00 libmitt.so.1 -> libmitt.so.1.0
-rwxr-xr-x 1 root root 5177 31 mar 23.59 libmitt.so.1.0

[root@dumburk c]# ldd /usr/local/bin/mittest
        linux-gate.so.1 =>  (0x00258000)
        libmitt.so.1 => /usr/local/mitt/lib/libmitt.so.1 (0x002ff000)
        libc.so.6 => /lib/libc.so.6 (0x009f0000)
        /lib/ld-linux.so.2 (0x009d3000)


Om ldd säger "libmitt.so.1 => not found" så beror det på att
biblioteksfilen inte kan hittas. Kontrollera i sådana fall att
katalogen som libmitt ligger i finns med i /etc/ld.so.conf eller i en
av filerna i katalogen /etc/ld.so.conf.d/ . Kör därefter ldconfig. Alternativt sätt rpath. För att sätta rpath måste man tala om för länkaren i samband med kompilering att den ska sätta rpath vilket görs med -Wl,-rpath,/path/to/lib till gcc. Exempel:
Kod: Markera allt
gcc fil1.c fil2.c -L /opt/foo/lib  -lfoo -Wl,-rpath,/opt/foo/lib -o program

Se även avsnittet om länkaren i introduktion till kompilatorn gcc.


Om rpath inte är satt i programbinären kan man använda kommandoskalsvariabeln LD_LIBRARY_PATH för att peka ut kataloger som bilioteksladdare ska leta i.
Kod: Markera allt
[kjell-e@dumburk c]$ export LD_LIBRARY_PATH=/usr/local/mitt/lib/:/opt/foo/lib

Därefter kan man starta programmet. Skal-variabeln LD_LIBRARY_PATH fungerar på liknande sätt andra PATH*-variabler med en kolonseparerad lista av sökvägar. Om rpath är satt i programbinären kommer den att användas före LD_LIBRARY_PATH.


Om man istället sätter runpath kan man använda LD_LIBRARY_PATH där sökvägarna i variabeln går före runpath i sökordningen. Om man sätter både rpath och runpath kommer runpath att användas istället för rpath.
Kod: Markera allt
[kjell-e@dumburk c]$ gcc -Wl,-R/opt/foo/lib -Wl,--enable-new-dtags -lfoo
Här används -R för att sätta rpath till i det här fallet /opt/foo/lib. Flaggan --enable-new-dtags gör att runpath och rpath sätts till samma söksträng. Om både rpath och runpath är satta kommer rpath att ignoreras och runpath att användas.

När ett program startas och dynamiska bibliotek ska läsas in då är sökordningen följande:
När RUNPATH är specificerad.
  1. LD_LIBRARY_PATH
  2. runpath (DT_RUNPATH-fältet)
  3. ld.so.cache
  4. standardbibliotekssökvägarna (/lib och /usr/lib)
När runpath inte är angiven (DT_RUNPATH är en tom sträng) och rpath eventuellt används.
  1. RPATH för laddat bibliotek följt av RPATH i programbinären.
  2. LD_LIBRARY_PATH
  3. ld.so.cache
  4. standardbibliotekssökvägarna (/lib och /usr/lib)
För mer detaljer se the Linux loader man page som finns på https://man7.org/linux/man-pages/man8/ld.so.8.html


För bibliotek vill man alltid ha en headerfil så att de som ska använda biblioteket har alla nödvändiga funktionsdeklarationer och makro-definitioner tillgängliga så att de kan använda biblioteket. Exempel mitt.h
Kod: Markera allt
[kjell-e@dumburk c]$ cat mitt.h
#ifndef MITT_H
#define MITT_H
int mitt(int);
#endif



Ett problem kan vara att som standard i Linux exporteras alla globala symboler. Om man har symboler, t.ex. någon funktion, som ska vara lokal och inte synas utanför biblioteket då behöver man se till att den inte exporteras. Standard i Linux när man kompilerar är att allt exporteras, d.v.s. som om man anger
-fvisibility=default
till kompilatorn gcc. Genom att istället ange
-fvisibility=hidden
till gcc kommer alla symboler att vara osynliga utåt som standard. I sin kod kan man då använda __attribute__ ((visibility("default"))) till de symboler som ska exporteras. För en funktion ska denna stå någonstans före funktionsnamnet.

Enklast kan vara att definiera ett makro i en headerfil. Exempel (sharedLibExports.h):
Kod: Markera allt
#ifndef SHAREDLIBEXPORTS_H
#define SHAREDLIBEXPORTS_H

#if 1
#define FOR_EXPORT __attribute__ ((visibility("default")))
#else
#define FOR_EXPORT
#endif

#endif



Detta makro kan sedan användas för att exportera de symboler som ska exporteras. I exemplet nedan (libfoo.c) exporteras endast funktionen foo. Övriga funktioner är lokala interna funktioner i biblioteket.

Kod: Markera allt
#include "sharedLibExports.h"

int mylocalfoo1(int a)
{
   return a*a;
}

int mylocalfoo2(int a)
{
   return a*2;
}

int FOR_EXPORT foo(int a)
{
   return mylocalfoo1(a)+mylocalfoo2(a);
}



Kod: Markera allt
[kjell-e@dumburk c]$ gcc -c libfoo.c -fPIC -fvisibility=hidden
[kjell-e@dumburk c]$ gcc -shared -Wl,--soname,libfoo.so.1 libfoo.o -o libfoo.so.1.0


Med nm -D går det nu att se vilka symboler som exporteras och där finns inte de lokala funktionerna med men funktionen foo finns där.


Kod: Markera allt
[kjell-e@dumburk c]$ nm -D libfoo.so.1.0
0000000000201020 B __bss_start
                 w __cxa_finalize
0000000000201020 D _edata
0000000000201028 B _end
00000000000005d4 T _fini
00000000000005a8 T foo
                 w __gmon_start__
0000000000000470 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable


Det går även att lägga till -fvisibility-inlines-hidden till gcc vid kompilering för att som standard göra alla inline funktioner gömda.


En annan möjlighet är att använda pragma GCC visibility. Detta görs typiskt i en headerfil. Det man gör är att sätta #pragma GCC visibility push(hidden) före och #pragma GCC visibility pop efter det som ska gömmas. Nedan visas del av en headerfil som exempel.
Kod: Markera allt
#pragma GCC visibility push(hidden)
void privatefunction_1(void);
void privatefunction_2(void);
...
void privatefunction_N(void);
#pragma GCC visibility pop


Exempel för biblioteket libfoo.
Filen libfoo.h:
Kod: Markera allt
#ifndef LIBHIDDEN_H
#define LIBHIDDEN_H

/* exported symbols */
int foo(int a);

/* hidden symbols */
#pragma GCC visibility push(hidden)
int foo1(int a);
int foo2(int a);
#pragma GCC visibility pop

#endif


Filen libfoo.c:
Kod: Markera allt
#include "libfoo.h"
int foo(int a)
{
   return foo1(a)+foo2(a);
}

int foo1(int a)
{
   return a*a;
}

int foo2(int a)
{
   return a*2;
}


Kompilera och skapa biblioteksfilen.
Kod: Markera allt
[kjell-e@dumburk c]$ gcc -c -fPIC libfoo.c -Wunknown-pragmas
[kjell-e@dumburk c]$ gcc -shared -Wl,--soname,libfoo.so.1 libfoo.o -o libfoo.so.1.0


Kontrollera vilka symboler som är synliga.
Kod: Markera allt
[kjell-e@dumburk c]$ nm -D libfoo.so.1.0
0000000000201020 B __bss_start
                 w __cxa_finalize
0000000000201020 D _edata
0000000000201028 B _end
00000000000005d4 T _fini
000000000000058a T foo
                 w __gmon_start__
0000000000000470 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable





Se även "How To Write Shared Libraries" som även finns på http://people.redhat.com/drepper/dsohowto.pdf
För mer om länkning se https://man7.org/linux/man-pages/man1/ld.1.html
För mer om dynamic linker/loader se https://man7.org/linux/man-pages/man8/ld.so.8.html

En omfattande bok som tar upp det mesta av det man behöver veta om kompilering och biblioteksfiler är boken
Advanced C and C++ Compiling av Milan Stevanovic.


En annan mycket bra bok om man ska hålla på med systemprogrammering i Linux är
The Linux Programming Interface av Michael Kerrisk.
Användarvisningsbild
kjell-e
 




Copyright © 2010-2024 Kjell Enblom.
This document is covered by the GNU Free Documentation License, Version 1.3

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".