Skywalker13

Diary of an ex-GeeXboX developer…

La biblioth√®que des ELFs ~ ūüáęūüá∑

Posted at — Dec 5, 2010

Hello,

Il y a beaucoup de choses que l’on apprend pas du tout dans les cours de programmation, et m√™me en suivant une haute √©cole pour une formation en tant qu’ing√©nieur. Heureusement les logiciels libres permettent de c√ītoyer des sp√©cialistes qui ont une exp√©rience pratique du logiciel, contrairement √† de nombreux professeurs qui ne connaissent que la th√©orie mais ne semblent pas vraiment pratiquer et passent ainsi √† c√īt√© de potentiels probl√®mes importants. Je pense tout particuli√®rement au C dans le contexte des ex√©cutables ELF utilis√©s par les OS Unix-like. Bien que beaucoup d’√©coles restent encore trop rattach√©es sur les technologies Microsoft, dans le monde des syst√®mes embarqu√©s GNU/Linux est particuli√®rement pr√©sent. N√©anmoins la formation sur le langage C n’est pas toujours tr√®s bonne. Une erreur trop courante concerne les espaces de nom utilis√©s par les biblioth√®ques.

Les fonctions “static”

Quand j’√©tudiais le C++ √† l’√©cole d’ing√©nieur on devait travailler avec un outil que je d√©teste absolument, c’est Rhapsody. Un g√©n√©rateur de code √† partir d’UML. L’UML c’est tr√®s bien pour poser les id√©es et r√©fl√©chir s√©rieusement sur un programme. Mais le fait qu’un langage de programmation ne soit pas objet ou orient√©-objet n’emp√™che pas de se donner des r√®gles reposants sur les concepts objets. Bref, Rhapsody c’est pire que tout, il g√©n√®re du code (C, C++ ou autre) √† partir d’UML. Finalement le programmeur fait du dessin, des carr√©s, des ronds et les relie avec des fl√®ches. Il presse un bouton et des centaines de lignes de code se g√©n√®rent. Wouhaw.. moi qui aime programmer.. Bient√īt on devra se former en tant que graphiste avant de r√©aliser un logiciel :-).

OK vous l’aurez compris, je d√©teste √ßa. Mais l√† o√Ļ je veux en venir c’est √† une petite anecdote. On travaillait donc sur ce logiciel et un des objet √©tait instanci√© une seule fois (donc en tant que Singleton. Alors je demande √† l’assistant qui √©tait pr√©sent avec nous pour r√©pondre √† nos questions :

Qu’est-ce que c’est concr√®tement un Singleton ?

Il s’assied et m’explique ce que je sais d√©j√†, c’est √† dire que l’objet est instanci√© qu’une seule fois. Il me montrait via l’interface de Rhapsody comment on param√®tre la classe pour √™tre un Singleton. Et me r√©-expliquait toujours la m√™me chose. J’essayais de reformuler ma question en vain. C’est finalement en allant observer le code C g√©n√©r√© que j’ai vu l’objet instanci√© en tant que variable globale statique.

5 secondes pour lire le code et comprendre, 15 minutes d’explication dans le monde “merveilleux” de Rhapsody et des formes g√©om√©triques.

Mais revenons à nos fonctions statiques

Une chose que tout le monde apprend aux cours c’est qu’une variable d√©clar√©e comme √©tant “static” dans une fonction, garde sa valeur entre chaque appel de fonction. Par contre je n’ai jamais eu un seul professeur qui nous ait expliqu√© √† quoi sert une variable globale statique, ou alors une fonction statique (sauf les fonctions statiques en C++, mais ici je fais r√©f√©rence au C).

En fait, il est tr√®s rare de voir un professeur proposer de cr√©er une biblioth√®que pendant un cours. En principe on cr√©er des ex√©cutables avec un main. En C je n’ai jamais vu (en dehors des logiciels libres et des professionnels), des fonctions d√©clar√©es comme √©tant statiques. Que se sois du code cr√©√© par des professeurs ou par des √©tudiants. Pourtant l’√©criture de biblioth√®ques demandent quelques consid√©rations suppl√©mentaires qui concernent les espaces de nom et plus particuli√®rement les ELF.

Mais tout d’abord une fonction “static” est une fonction qui n’est pas “extern” (qui est le qualificateur de type par d√©faut). En √©tant “static” la visibilit√© de la fonction est restreinte au fichier source o√Ļ elle a √©t√© d√©clar√©e. Une fonction externe peut √™tre atteignable depuis n’importe o√Ļ dans les sources. Mais dans le cas des ELF c’est encore plus subtile que √ßa car elle peut √™tre atteinte depuis n’importe quelles biblioth√®ques ou applications √©tant li√©es avec elle.

Les Shared Object (so) et les DLL

Sans partir dans des explications compliquées et inutiles il faut bien faire la différence entre une DLL (de chez Microsoft) et un Shared Object (qui vient du monde Unix). Les développeurs pour Windows connaissent bien :

__declspec(dllexport)

Qui est un qualificateur de type invent√© par Microsoft. Il permet de sp√©cifier les fonctions qui doivent √™tre export√©es par la DLL. Vous pouvez aussi utiliser un fichier .def qui donne la liste des fonctions √† exporter. Ce principe existe aussi avec les gcc >=4 mais est rarement utilis√© √† ma connaissance. Quoi qu’il en soit dans tous les cas vous avez une liste de symboles export√©s.

La diff√©rence qui m’int√©resse c’est au runtime, lorsque le lanceur d’application doit charger les biblioth√®ques. Dans le cas d’une DLL, le programme va rechercher les pointeurs sur les fonctions d√©sir√©es √† l’aide de LoadLibrary. C’est lourd mais √ßa fonctionne. Dans le cas d’un “so” le fonctionnement est tr√®s diff√©rent. Le lanceur de programme de Linux va charger les biblioth√®ques les unes apr√®s les autres dans l’ordre o√Ļ elles ont √©t√© li√©es. Lorsque le programme a besoin d’une fonction, c’est la premi√®re occurrence trouv√©e qui sera utilis√©e.

Concrètement

Imaginons un programme qui utilise libplayer et libvalhalla. Si je n’√©tais pas conscient du probl√®me que je viens d’expliquer, dans ces deux biblioth√®ques j’aurais pu √©crire une fonction qui a exactement le m√™me nom (de part et d’autre) comme par exemple :

libplayer :

void foobar (int a, int b);

libvalhalla :

void foobar (int c);

Ces fonctions ne sont pas static car bien entendu j’aimerais les utiliser partout dans les projets. Alors que se passe-t-il lorsqu’on lie l’application sur ces deux biblioth√®ques ? Notez que ces deux fonctions ne sont pas non plus d√©clar√©es dans les en-t√™tes “publiques” que vous distribuez √† vos d√©veloppeurs. Par exemple vous donnez ceci √† un ami :

player.so (que vous avez compilé vous même)

Et un fichier d’en-t√™te

player.h

/* libplayer header */
void libplayer_is_the_best (void);

De même avec la seconde bibliothèque :

valhalla.so

valhalla.h

/* libvalhalla header */
void libvalhalla_is_the_best (void);

Maintenant vous avez créé une application tel que :

#include <player.h>
#include <valhalla.h>

int
main (void)
{
  libplayer_is_the_best ();
  libvalhalla_is_the_best ();
  return 0;
}

Dans libplayer.so il y a le symbole “foobar”, mais il existe √©galement dans la biblioth√®que libvalhalla.so. Lorsque vous liez votre application vous n’avez aucune erreur. Vous utilisez deux fonctions consid√©r√©es comme publiques et qui n’ont pas du tout le m√™me nom. Alors o√Ļ est le probl√®me? Et bien c’est tr√®s simple. Quand vous liez votre programme vous devez passer les noms des biblioth√®ques. Par exemple:

cas 1 : gcc -lplayer -lvalhalla main.c -o main

Mais vous auriez aussi pu faire

cas 2 : gcc -lvalhalla -lplayer main.c -o main

Les deux fa√ßons sont correctes mais le comportement de l’application main n’est pas du tout le m√™me. Les deux biblioth√®ques utilisent la fonction foobar. Cette fonction n’a pas le m√™me nombre d’arguments dans libvalhalla que dans libplayer et leurs comportements sont diff√©rents. Les fonctions libplayer_is_the_best et libvalhalla_is_the_best utilisent “en th√©orie” leurs propres fonction foobar. Et bien en r√©alit√© ce n’est pas le cas.

Dans le cas 1, le chargeur de programme va commencer par player.so, puis par valhalla.so. Lorsque la fonction libplayer_is_the_best va utiliser foobar, alors le foobar de player.so va √™tre utilis√©. Mais lorsque valhalla.so va utiliser foobar, c’est aussi le foobar de player.so qui sera utilis√© (a√Įe). Dans le cas 2 c’est le m√™me principe mais invers√©. Les cons√©quences peuvent √™tre tr√®s impr√©visibles.

Si vous avez une application qui est liée à des dizaines de bibliothèques il faut espérer que tout le monde ait pris la peine de faire deux choses importantes :

  1. Utilisez toujours un espace de nom pour vos fonctions. Par exemple libvalhalla en utilise trois (libvalhalla_, valhalla_ et vh_). Un espace de nom en C ce n’est rien d’autre qu’un nom identique que vous concat√©nez au d√©but des noms. Par exemple nos foobar auraient p√Ľt se nommer libplayer_foobar et libvalhalla_foobar, ce qui aurait √©vit√© la collision.

  2. D√©clarez toujours en static toutes les fonctions qui ne sont pas utilis√©es en dehors du fichier source. Une fonction static n’a pas besoin d’espace de nom, car elle n’est jamais export√©e! Et faire ainsi permet d’aider le compilateur √† effectuer de meilleurs optimisations.

Comment détecter et debugger les collisions

Une des solution c’est de compiler votre programme enti√®rement en static. Il doit donc √™tre li√© aux .a de toutes les biblioth√®ques. Dans ce cas de figure, une collision sera forc√©ment d√©tect√©e par le linker.

Pour debugger commencez par tout compiler en -O0 -g3, puis utilisez valgrind. Vous arriverez √† remonter sur l’appel de fonction qui s’est fait de la libA √† la libB. Vous pourriez voir le foobar de player.so appel√© par valhalla.so.

En pratique…

Il y a de nombreux mois, j’ai eu des probl√®mes de ce type avec libVLC et libplayer, car les fonctions de getopt √©taient export√©es par libVLC bien que c’√©tait uniquement pour son propre usage. √áa me provoquait des collisions avec le getopt que j’utilise dans libplayer-test. J’ai bien entendu rapport√© le probl√®me qui a √©t√© corrig√©.

J’ai aussi eu un cas avec GeeXboX et le projet GuPNP. Le d√©veloppeur principal a aussi √©t√© pr√©venu mais a priori il s’en fiche (ce n’est pas moi qui l’a contact√© mais un autre du team). Du coup ce n’est pas possible de lier en static si on utilise deux de ses libs car elles ont les m√™me fonctions non-static pour traiter le XML. Et le pire c’est que le nom de ces fonctions n’a pas d’espace de nom tr√®s original. Notez qu’en dynamique il n’y a jamais de probl√®me car heureusement dans les deux biblioth√®ques, les fonctions sont les m√™mes.

Bref, en fouillant bien on doit trouver ce genre d’exemple un peu partout…

J’esp√®re que ce poste vous sera utile pour vos propres d√©veloppements.

Bon code et √† bient√īt!