Vous êtes sur la page 1sur 10

ISET Rades/L2 SEM/Linux embarqué A.

U : 22/23

Complément de cours : librairies statiques et dynamiques

Linux embarqué

Ce document porte sur les librairies, leur utilisation et la manière de les générer.

1. Les fichiers objet

Les premières étapes de la compilation, c’est-à-dire toutes celles qui se situent avant celle de l’édition
de liens, consistent à transformer les fichiers dits sources (extension .c) en fichiers dits objet (extension .o).
Prenons par exemple le fichier source alloctabint.c suivant :

#include <stdlib.h>

int* alloctabint(int nb)

int *tab;

tab =
(int*)malloc(nb*sizeof(int));
if(tab==NULL)

{ perror("malloc"); exit(1); }
return(tab);

La commande

gcc -c alloctabint.c -o alloctabint.o

génère un fichier alloctabint.o qui est un fichier objet. Les fichiers objet contiennent des informations
codées en binaire. Ces informations représentent le programme contenu dans le fichier source dans un
format presque exécutable. En particulier :

– si le fichier source contient des appels à des fonctions qui ne sont pas définies dans ce fichier (dans
notre exemple les fonctions malloc, exit, et perror) alors ces appels sont représentés par des
branchements à des adresses mémoires restant à déterminer ultérieurement ;
– si le fichier source contient des déclarations de variables extern, alors l’adresse mémoire de ces
variables reste également indéterminée.

Par ailleurs, la seule chose qui pourrait manquer à un fichier objet pour être un fichier exécutable à part
entière, est une fonction
1
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

main.

Étant donné un fichier objet contenant une fonction main, et des fichiers objet où seraient définies les
fonctions exit, malloc et perror, l’éditeur de liens calcule l’adresse mémoire de ces fonctions et les
met à la disposition de la fonction alloctabint qui pourra alors faire des branchements vers ces
fonctions. L’éditeur de liens calcule aussi l’adresse mémoire de la fonction taballocint. S’il existe
des fonctions qui appellent alloctabint, l’éditeur de lien leur fournira l’adresse de cette fonction ce
qui leur permettra de faire des branchements vers elle. Enfin, après l’édition de liens, le processeur aura
accès à l’adresse de la fonction main ce qui lui permettra de lancer l’exécution du programme.

2. Les librairies
2.1 Description

Les librairies contiennent la définition de fonctions susceptibles d’être utilisées par plusieurs
programmes (simultanément ou non). Il existe deux types de librairies : les librairies statiques et les
librairies dynamiques. Le nom des librairies statiques est typiquement de la forme :

lib***.a

Par exemple en regardant dans le répertoire /usr/lib ou /usr/lib/x86_64-linux-gnu/ vous


trouverez :

– libc.a est la librairie standard C (fonctions malloc, exit, etc.) ;


– libm.a est la librairie mathématique (fonctions sqrt, cos, etc.) ;
– libjpeg.a est la librairie des fonctions permettant de manipuler les fichiers image au format jpeg ;
– libGL.a est la librairie des fonctions permettant de manipuler des objets 3D ;
– etc.

Le nom des librairies dynamiques est typiquement de la forme :

lib***.so

Dans le répertoire /usr/lib ou /usr/lib/x86_64-linux-gnu/ vous trouverez également les


versions dynamiques des librairies ci-dessus.

– Utiliser des librairies statiques, revient à inclure la définition des fonctions de la librairie dans
votre fichier exécutable, pendant l’étape de l’édition de liens (donc pendant la compilation et
avant le lancement du programme).
– Utiliser des librairies dynamiques, revient à indiquer à votre programme l’emplacement d’où
il pourra charger en mémoire ces définitions après le lancement du programme.

L’avantage des librairies statiques est que le fichier exécutable qui en résulte contient, avant
l’exécution, tout ce qui lui est nécessaire pour fonctionner. Alors que, si une librairie dynamique a
disparu, ou a été considérablement modifiée, un programme exécutable qui s’exécutait parfaitement en
utilisant cette librairie peut devenir totalement inopérant.

Par contre, un programme obtenu par compilation avec une librairie statique a un fichier exécutable

2
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

beaucoup plus volumineux que le même programme obtenu par compilation avec une librairie
dynamique, puisque la définition des fonctions de la librairie ne se trouve pas dans le fichier exécutable.

Enfin, si une librairie statique est mise à jour alors, tout programme l’utilisant devra être recompilé
pour qu’il puisse prendre en compte la modification. Dans le cas d’une librairie dynamique, cette mise à
jour n’a pas besoin de recompilation.

2.2 Utilisation

a. Principe général

Soit la librairie libXXX.a (ou libXXX.so) se trouvant dans un répertoire dont le chemin absolu est
chemin. Pour compiler un fichier source prog.c faisant appel à des fonctions de cette librairie, il faut taper la
ligne de commande suivante :

gcc prog.c -Lchemin -lXXX -o prog

autrement dit, après l’option -l, il faut mettre le nom de la librairie sans l’extension (donc .a , .so) et sans le le
préfixe lib.

– Pour la librairie libjpeg.a, l’option de compilation est -ljpeg ;


– pour la librairie libGL.so, l’option de compilation est -lGL ;
– pour la librairie libsocket.so.2, l’option de compilation est -lsocket ;
– et ainsi de suite.

Si le programme contient plusieurs fichiers source : prog1.c, prog2.c, prog3.c, ..., progn.c, progmain.c,
alors il faudra la suite de commandes suivante :

gcc -c prog1.c -o prog1.o

gcc -c prog2.c -o prog2.o

gcc -c prog3.c -o prog3.o

...

gcc -c progn.c -o progn.o

gcc -c main.c -o main.o

gcc prog1.o prog2.o prog3.o ... progn.o main.o -Lchemin -lXXX -o prog

On peut aussi simplement exécuter :

gcc prog1.c prog2.c prog3.c ... progn.c main.c -Lchemin -lXXX -o prog

b. Exemple

3
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

Soit le programme suivant qui calcule et affiche la racine carrée du nombre qu’on lui fournit en
entrée. On utilise pour cela, la fonction sqrt qui a été défini dans le fichier libm.a, mais aussi dans le fichier
libm.so qui se trouvent tous deux dans le répertoire /usr/lib. Voici le fichier prog.c :

#include <math.h> int


main()

double in; scanf("%f",&in);

printf("%f\n", sqrt(in));
return 0;

Pour compiler ce programme, il faut exécuter la commande :

gcc prog.c -L/usr/lib -lm -o prog

La question qui peut se poser est pourquoi on ajoute pas le drapeau -lm loesqu’il s’agit des bibliothèques
stdio.h, stdlib.h, conio.h, … ?

En effet, stdio, stdlib , conio, font partie de la bibliothèque standard du langage C. L'emplacement exact de
la bibliothèque standard dépend du système, mais elle se trouve souvent dans un fichier appelé libc.a ou
libc.so.

Fondamentalement, libc (la bibliothèque C standard) est chargée par défaut, prête à être liée, de sorte que
tous les fichiers d'en-tête de la bibliothèque standard sont là pour que le préprocesseur puisse les charger
dans votre code source.

Libc se trouve dans le répertoire /usr/lib/x86_64-linux-gnu. Il ya une version statique et une autre dynamique
(/usr/lib/x86_64-linux-gnu/libc.a / /usr/lib/x86_64-linux-gnu/libc.so)

D’autres bibliothèques (non standards) ne sont pas incluses dans libc. Par exemple la bibliothèque
mathématique (libm) qui contient toutes les fonctions liées à la manipulation des variables de type float, et
les fonctions mathématiques en virgule flottante. Ces fonctions étaient assez volumineuses et gourmandes en
ressources processeur par rapport à la puissance de calcul disponible, et les programmeurs ne voulaient pas
charger libm par défaut pour cette raison.

Même aujourd'hui, à l'ère des superordinateurs portables, il faut ajoutons le drapeau -lm à la commande gcc
afin d'inclure<math.h>, ce qui est nécessaire pour utiliser les fonctions mathématiques en virgule flottante
disponibles dans la bibliothèque C standard.

Donc, si on souhaite utiliser les fonctions de la bibliothèque mathématique en C, il ne suffit pas de placer
#include<math.h> au début du code source, mais il faut ajouter le drapeau -lm à la commande du
compilateur gcc afin d'utiliser les fonctions mathématiques dans votre code C.

4
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

c. Différence d’utilisation des librairies statiques et dynamiques

Dans ce qui précède, nous n’avons fait aucune distinction entre les librairies statiques et
dynamiques. En effet l’utilisation des deux types de librairies est presque identique. Il y a pourtant une
petite différence : dans le cas des librairies dynamiques, si le programme allait toujours chercher les
librairies au même emplacement, il suffirait de changer cet emplacement pour que le programme
devienne inutilisable1, ou qu’il faille le recompiler. C’est pourquoi pour chercher l’emplacement des
librairies dynamiques, on s’aide d’une variable d’environnement appelée

LD_LIBRARY_PATH

Cette variable indique au programme à quels emplacements il doit chercher les librairies dynamiques.
Si cet emplacement est modifié, il suffit de modifier la variable, sans changer le programme. Pour
indiquer au système qu’il faut chercher dans le répertoire

/usr/local/lib, il faudra initialiser la variable LD_LIBRARY_PATH de la manière suivante :

export LD_LIBRARY_PATH=/usr/local/lib

Si l’on veut que les programmes cherchent dans /usr/local/lib, dans /usr/X11R6/lib et dans le
répertoire courant, il faudra écrire :

export LD_LIBRARY_PATH=.:/usr/X11R6/lib:/usr/local/lib

En pratique, vous ne définirez jamais cette variable mais vous ajouterez des fichiers à sa définition :

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/users/profs/habibi/libs

2.3 Création de librairies

Soient les fichiers prog1.c, prog2.c, prog3.c, ..., progn.c contenant des fonctions (autres que
main). Nous voulons mettre ces fonctions dans une librairie pour que d’autres programmes puissent les
utiliser. Dans un premier temps, il est nécessaire de compiler ces fichiers pour obtenir des fichiers objet
(cf. section 1).

gcc -c prog1.c -o
prog1.o gcc -c
prog2.c -o prog2.o
gcc -c prog3.c -o
prog3.o

...

gcc -c progn.c -o progn.o

a. Création une librairie statique

1
Sous linux, cette variable n’est pas prise en compte, il faut que le nom de la librairie se trouve dans le fichier /etc/ld.so.conf et que
ldconfig ait été lancé après la modification de ce fichier.
5
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

Pour créer une librairie statique à partir des fichiers objet, il faut utiliser la commande ar qui archive ces
fichiers dans un seul fichier. L’option -r permet d’insérer les nouveaux fichiers dans l’archive. L’option -v
(verbose) permet d’afficher à l’écran le nom des fichiers insérés.

ar -rv libtoto.a prog1.o prog2.o prog3.o ... progn.o

La librairie libtoto.a est prête à être utilisée dans une compilation.

Pour créer une librairie dynamique à partir des fichiers objet, on peut utiliser gcc avec
l’option -shared. gcc -o libtoto.so -shared prog1.o prog2.o
prog3.o ... progn.o

La librairie libtoto.so est prête à être utilisée dans une compilation.

b. L’emplacement d’une librairie

En général, on place une librairie (ou un lien vers cette librairie) à un emplacement visible par tous les
programmes qui sont susceptibles de l’utiliser. Typiquement dans :

– /usr/local/lib si la librairie est susceptible d’être utilisée par plusieurs utilisateurs ;


– ~/lib si la librairie est susceptible d’être utilisée par un seul utilisateur.

Dans le cas d’une librairie dynamique, il faudra penser à vérifier que la variable d’environnement
LD_LIBRARY_PATH permettra aux programmes de trouver la nouvelle librairie.

Exemples :
Vous trouverez ci-dessous 2 fonctions qui affichent ”Bonjour” et ”Au revoir” autant de fois que le nombre
passé en argument :
void bonjour(int nbre)
{
int i;
for(i=0;i<nbre;i++) printf("Bonjour ...\n");
return;
}
void revoir(int nbre)
{
int i;
for(i=0;i<nbre;i++) printf("Au revoir ...\n");
return; }
Placez ces fonctions dans des fichiers nommés bonjour.c et revoir.c. Compilez ces fichiers afin de générer
les fichiers objet :
gcc -c bonjour.c revoir.c Des avertissements apparaissent (warning)…
La commande ls montrera que le compilateur a crée deux fichiers objets en code machine bonjour.o et
revoir.o.
Il est maintenant possible de lier ces fichiers objet lors d’une compilation. Il suffira de les déclarer dans un
fichier header, la compilation ne sera plus nécessaire d’ou un gain de temps.
libsalut.h
#ifndef libslt
6
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

#define libslt
void bonjour(int nbre) ;
void revoir(int nbre) ;
#endif
Tapez le programme suivant qui appelle des fonctions de bonjour.c et revoir.c dans le fichier exemple2.c :
#include <stdio.h>
#include "libsalut.h"
int main(void)
{
int a=0;
printf("Donnez un nombre entre 1 et 5 compris: ");
scanf("%d",&a);
printf("\n");
bonjour(a);
revoir(a);
return(0); }
La compilation peut se faire par : gcc exemple2.c bonjour.o revoir.o
Ici seul exemple2.c est compilé pour générer exemple2.o qui sera lié à salut.o et revoir.o
Les fichiers objet *.o peuvent être regroupés dans une bibliothèque.
Créez la librairie libsalut.a (les noms des bibliothèques commencent toujours par lib) à l’aide de la
commande ar qui crée un fichier archive : ar r libsalut.a bonjour.o revoir.o
Vous pouvez vérifier le contenu de votre librairie en tapant : ar t libsalut.a
Utilisation de la librairie
A partir de cette étape, vous pouvez déjà utiliser ces fonctions en respectant quelques conditions.
La première de celles-ci est de placer libsalut.a dans le répertoire où vous effectuez votre compilation. La
seconde est de compiler votre programme en spécifiant le nom de la librairie.
Compilez ce programme en exécutant la ligne suivante : gcc -o exemple exemple2.c libsalut.a
Vous pouvez également placer toutes vos librairies dans un de vos répertoires (par exemple,
« librairies » que vous créerez par mkdir librairies).
Vous lancerez alors votre compilation de la manière suivante :
gcc -o exemple exemple2.c libsalut.a ou gcc -o exemple exemple2.c -L librairies -l salut
-L indique au linker un nouveau chemin pour les bibliothèques -l indique le nom de la bibliothèque,
remarquez que le nom de la bibliothèque libsalut.a devient salut dans la ligne de commande.

Lecture : utilitaires supplémentaires pour l'administration des bibliothèques partagées

 la commande ldd <prog> permet de savoir quelle(s) bibliothèque(s) partagée(s) le programme


<prog> utilise ;
 la commande ldconfig régénère tous les liens symboliques dans les répertoires contenant des
bibliothèques partagées (ces répertoires sont répertoriés dans le fichier /etc/ld.so.conf ). Les
répertoires /lib et /usr/lib sont pris en charge par défaut. L'usage des liens symboliques permet de
conserver plusieurs versions de chaque bibliothèque, tout en passant de l'une à l'autre en un
seul appel système : ainsi la mise à jour d'une bibliothèque peut se faire sans arrêter le système !
ldconfig -v affiche l'ensemble des librairies installées dans le système.
 La commande export LD_librarie_PATH=`pwd` permet de rajouter le répertoire courant au
chemin de recherche des librairie partagées (exactement comme l'option -L. de gcc)
 La commande ltrace permet de suivre les appels à des fonctions de bibliothèques dynamiques
effectués par un programme, exactement de la même façon que strace le fait pour les appels
système : de même que strace, c'est un outil précieux pour étudier le comportement d'un
7
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

programme qui ne fonctionne pas correctement.

Exemple : si le programme a.out a été compilé en utilisant la bibliothèque partagée libtoto.so, alors la
commande ltrace a.out renvoie :
libc_start_main(0x0804850c, 1, 0xbffff6c4, 0x08048580, 0x080485d0 <unfinished ...>
printf("---- D\351but du programme princ"...---- Début du programme principal ----
) = 39
ma_fonction(0x40016640, 0x080485d0, 0xbffff698, 0x4003e95d, 1 <unfinished.....>
printf("Bienvenue dans Ma Fonction !\n"Bienvenue dans Ma Fonction !
) = 29
<... ma_fonction resumed> ) = 29
printf("Ma_variable vaut : %i\n", 5Ma_variable vaut : 5
) = 21
mon_addition(6, 7, 0xbffff638, 0x08048531, 0x40016640 <unfinished.......>
printf("Bienvenue dans Mon Addition !\n"Bienvenue dans Mon Addition !
) = 30
<... mon_addition resumed> ) = 13
printf("6+7=%i\n", 136+7=13
) =7
printf("---- Fin du programme principal "...---- Fin du programme principal ----
) = 37
+++ exited (status 0) +++
La commande ltrace -l libtoto.so a.out renvoie :
---- Début du programme principal ----
ma_fonction(0x40016640, 0x080485d0, 0xbffff698, 0x4003e95d, 1Bienvenue dans Ma Fonction !
) = 29
Ma_variable vaut : 5
mon_addition(6, 7, 0xbffff638, 0x08048531, 0x40016640Bienvenue dans Mon Addition !
) = 13
6+7=13
---- Fin du programme principal ----
+++ exited (status 0) +++
Et la commande ldd a.out renvoie :
linux-gate.so.1 => (0xffffe000)
libtoto.so => /usr/lib/libtoto.so (0x40027000) libc.so.6 => /lib/tls/libc.so.6
(0x40029000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
On voit bien à travers cette dernière commande que le programme a.out fait appel à la librairie
partagée libtoto.so.

8
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

Utilisation de librairies existantes dans le système

La plupart des fonctions élémentaires dont un programmeur a besoin sont généralement déjà
programmées dans des librairies installées. Par exemple, pour afficher une image JPG il n'est nullement
besoin de ré-écrire un décodeur JPG : il suffit de faire appel à une fonction d'affichage d'image JPG. Oui,
mais laquelle, et dans quelle librairie se trouve-t-elle ? Pareil pour lire un fichier MP3, ou encore pour
accéder au réseau.

1 - Comment connaître les librairies disponibles dans le système ?

La commande ldconfig -v affiche l'ensemble des librairies installées dans le système. Recherchons une
librairies contenant des routines relatives au traitement des images JPEG :

ldconfig -v | grep jpeg

On constate que la librairie libjpeg.so est installée dans le système. Où se trouve le fichier de librairie
dynamique libjpeg.so ?
whereis libjepg.so
On obtient comme résultat l'emplacement /usr/lib/libjpeg.so
2 - Comment connaître les fonctions disponibles dans une librairie du système ?
Quelles fonctions contient cette librairie libjpeg.so ? Pour cela, affichons sa table de symboles grâce à
objdump :
objdump -T /usr/lib/libjpeg.so
Parmi tous les résultats affichés, les fonctions de la librairies sont précédées du mot Base. Filtrons la
sortie de objdump grâce à grep afin d'affiner le résultat :
objdump -T /usr/lib/libjpeg.so | grep Base
On obtient alors la liste des 110 fonctions disponibles dans la librairie dynamique libjpeg.so
Parmi toutes ces fonctions on trouve la fonction jpeg_read_header (en gras ci-dessus) qui permet de lire
l'en-tête d'un fichier JPEG, afin de connaître, par exemple, la résolution en pixels de l'image. Avant
d'utiliser cette fonction pour afficher la résolution d'une image JPEG, il nous faut trouver dans le système
le fichier d'en-tête (avec l'extension .h) relatif à cette librairie partagée libjpeg.so.
3 - Où retrouver le fichier d'en-tête relatif à une librairie ?

Les fichiers d'en-tête relatifs aux librairies partagée du système, et que l'utilisateur peut utiliser dans ses
programmes en C, sont généralement enregistrés dans le répertoire /usr/include. Recherchons dans ce
répertoire un fichier dont le nom contient jpeg :
ls /usr/include/*jpeg*.h
On obtient alors le fichier /usr/include/jpeglib.h
ATTENTION : le fichier de libraire partagée s'appelle libjpeg.so mais le fichier d'en-tête s'appelle
jpeglib.h
Pour utiliser les fonctions et les types de la librairie libjpeg.so il suffit de rajouter #include <jpeglib.h>
au début du programme.
4 - Exemple d'utilisation de la librairie libjpeg.so :
Le programme en C suivant affiche la résolution d'un fichier image toto.jpg :

/* Ce programme test.c affiche la résolution */


/* d'une image toto.jpg en utilisant la librairie libjpeg.so */ #include <stdio.h>
#include <jpeglib.h>
9
ISET Rades/L2 SEM/Linux embarqué A.U : 22/23

int main(void)
{
struct jpeg_decompress_struct cinfo; struct
jpeg_error_mgr jerr;
FILE *file;
char *fichier="toto.jpg";

cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);

if ((file=fopen(fichier,"rb"))==NULL)
{
fprintf(stderr,"Erreur : impossible d'ouvrir le fichier texture.jpgn"); exit(1);
}
jpeg_stdio_src(&cinfo, file);
jpeg_read_header(&cinfo, TRUE);
printf("La largeur de l'image est %i pixels\n",cinfo.image_width); printf("La hauteur de l'image
est %i pixels\n",cinfo.image_height); return (0);
}
Pour le compiler on utilisera la ligne de commande suivante (l'éditeur de lien fait appel à la librairie
libjpeg.so) :
gcc -ljpeg test.c
Et si on lance l'exécutable a.out obtenu, on obtient à l'écran :
La largeur de l'image est 400 pixels La hauteur de
l'image est 342 pixels

Où trouver de l’aide Les fichiers d’aides (.doc) des différentes librairies installées dans le système se
trouvent dans le répertoire /usr/share/doc.

10

Vous aimerez peut-être aussi