wiki:InsiaProgCLib

Sommaire

Bibliothèques statiques et dynamiques

Le principe de "bibliothèque logicielle" (library) en programmation consiste à rassembler des fonctions pré-compilées en général en un seul fichier prêt à l'emploi. On distingue deux types de bibliothèques:

  • les bibliothèques statiques: elles peuvent être réutilisées par un programmeur et s'intégrer facilement dans la phase d'édition des liens.
  • les bibliothèques dynamiques: elles peuvent être réutilisées par un programme, et leur édition de lien est déjà faite.

1. Bibliothèques statiques

Il s'agit de simples portions de programme C qui ont été compilées jusqu'à l'avant-dernière phase, c'est-à-dire dont l'édition des liens n'a pas été faite. Avec gcc (gcc -c programme.c), on obtient facilement un fichier objet (extension .o) qui est une forme de bibliothèque statique.

En général, les fichiers objets sont rassemblés dans un seul fichier "archive" dont l'extension est .a. Ces archives se manipulent à l'aide de la commande ar, exemple:

$ ar t /usr/lib/libX11.a
AllCells.o
AllowEv.o
AllPlanes.o
...

Si on veut avoir la liste des fonctions compilées dans une archive, on peut utilise la commande nm:

$ nm /usr/lib/libX11.a
AllCells.o:
00000000 T XAllocColorCells
         U _XFlush
         U _XRead
         U _XReply
...

On obtient ainsi, fichier objet par fichier objet, le nom des fonctions utilisées (marqueur U) et définies (marqueur T).

Pour intégrer une bibliothèque statique dans son programme, il suffit de l'ajouter à la liste des fichiers objets lors de l'édition des liens:

$ gcc -c xstatique.c
$ gcc -o xstatique xstatique.o /usr/lib/libX11.a

L'usage des bibliothèques statiques est en général restreint à quelque cas précis, car elles ne servent qu'au programmeur, et de manère beaucoup moins utile qu'une bibliothèque dynamique.

2. Bibliothèques dynamiques

Il s'agit de programmes en C qui ont été compilés avec une édition de liens particulière (avec gcc: gcc -shared -o libtoto.so toto.c). Bien qu'ils aient un format similaire, ces programmes ne sont pas conçus pour être exécutés, mais pour être incorporés dans d'autre programme au moment de leur exécution. En particulier une bibliothèque dynamique n'a pas besoin du fameux point d'entrée main pour être valide.

On peut obtenir la liste des fonctions "fournies" par une bibliothèque à l'aide de la fonction nm -D:

$ nm -D /usr/lib/libX11.so
0003a200 T _X11TransBytesReadable
0003a840 T _X11TransClose
0003a800 T _X11TransCloseForCloning
...

Le format de sortie est bien entendu identique à celui décrit dans le cas d'une bibliothèque statique.

On peut facilement la réutiliser dans un programme, mais dans ce cas le code n'est de la bibliothèque n'est pas intégré, seuls les liens vers la bibliothèques sont enregistrés. Cela signifie que lors de son exécution, le système va remarquer que notre programme nécessite la bibliothèque dynamique et la charger automatiquement (ce que l'on vérifie avec ldd):

$ gcc -o xdynamique xdynamique.c -lX11 -L/usr/lib
$ ldd xdynamique
        linux-gate.so.1 =>  (0xffffe000)
        libX11.so.6 => /usr/lib/libX11.so.6 (0xb7e49000)
        ...

En pratique, cela signifie que l'on peut distribuer son programme indépendamment des bibliothèques qu'il utilise. C'est en général valable quand on peut maîtriser les "dépendances" facilement, comme dans une distribution Linux.

Chargement explicite

En général, le chargement et l'utilisation des bibliothèques dynamiques est implicite et vous n'avez pas à en gérer les détails. Il peut parfois être utile de pouvoir charger explicitement un bibliothèque à un moment quelconque de votre programme, et en exploiter certaines fonctions. C'est par exemple ainsi qu'on peut implémenter un système de plugin (c.-a.-d une portion de programme chargée au moment de l'exécution d'un programme si jugé nécessaire et/ou utile).

Ceci se fait à l'aide des fonctions dlopen et dlsym:

#include <dlfcn.h>

int main(int argc, char** argv) {
  void* bibli;
  double (*fonction_cos)(double);

  bibli        = dlopen("libm.so");
  fonction_cos = dlsym(bibli, "cos");
  printf("cos(PI)=%f", fonction_cos(3.1416));
  ...

Surchargement

Il est possible de demander au mécanisme de chargement automatique des bibliothèques dynamiques utilisées par un programme de modifier légèrement son comportement et charger au préalable une bibliotèque quelconque. Ceci peut en particulier servir à charger une bibliothèque qui contient des fonctions modifiées qui vont être utilisée de préférence par le programme.

Par exemple, si vous créez une bibliothèque libmyopen.so qui définit une fonction fopen et que vous invoquez un programme ainsi:

$ LD_PRELOAD=./libmyopen.so programme

... alors le programme utilisera votre fonction fopen au lieu de celle de la bibliothèque standard du C (qui est /lib/libc6.so).

3. API et ABI

  • API: Application Programming Interface, ce qui est nécessaire au programmeur pour s'interfacer avec la bibliothèque (prototypes, macros, documentation, etc). En C, toutes les informations sont en général regroupées dans les fichiers .h.
  • ABI: Application Binary Interface, ce qui est nécessaire à la machine pour s'interfacer avec la bibliothèque. Il s'agit d'un formalisme précis décrivant comment l'API est implémentée au niveau machine (ex: ordre de passage des paramètres d'une fonction, alignement des données, etc).

API et ABI sont deux notions distinctes mais très liées. Quand on utilise une bibliothèque logicielle précompilée (statique ou dynamique), pour vérifier la compatibilité ascendante, on considère l'ABI. Une API peut sembler "compatible" pour un programmeur et effectivement ne pas perturber la compilation du programme, alors qu'elle change fondamentalement le format binaire des appels. Exemple avec cette première version:

typedef float matrice[3][3];

void multiplie_matrice(matrice a, matrice b, matrice resultat);

Si on la remplace par cette seconde:

typedef double matrice[3][3];

void multiplie_matrice(matrice a, matrice b, matrice resultat);

La correspondance ABI/API n'est pas triviale. Dans le domaine des ABIs, on ne manipule que des "symboles" (des noms abstraits), qui peuvent représenter des éléments concrets pour le langage qui a été compilé: variable globale, fonction, constructeur/destructeur (C++), etc. On peut par exemple constater que l'ABI d'un programme C++ n'est pas directement lisible (il faut utiliser nm -CD pour obtenir la version "lisible"):

$ nm -D /usr/bin/apt-get
         U _ZN10pkgAcqFileC1EP10pkgAcquireSsSsmSsSsRKSsS3_
         U _ZN10pkgAcquire10WorkerStepEPNS_6WorkerE
         U _ZN10pkgAcquire11FetchNeededEv
         ...

Note: pour les curieux, vous pouvez consulter l'ABI du format binaire ELF utilisé par les exécutables et les bibliothèques dynamiques à l'aide de la page de manuel elf(5).

Last modified 14 years ago Last modified on Nov 20, 2006, 1:07:42 PM