79 votes

Comment gérer les collisions de symboles entre bibliothèques liées statiquement?

L'une des règles les plus importantes et les meilleures pratiques en matière de rédaction d'une bibliothèque, est de mettre tous les symboles de la bibliothèque dans une bibliothèque spécifique de l'espace de noms. C++ permet cela facilement, en raison de l' namespace mot-clé. Dans C l'approche habituelle est de préfixer les identificateurs avec une bibliothèque préfixe spécifique.

Les règles de la norme C de mettre des contraintes sur les personnes (pour la sécurité de la compilation): Un compilateur C peut regarder uniquement le premier 8 personnages d'un identifiant, donc, foobar2k_eggs et foobar2k_spam peut être interprété comme la même les identificateurs valablement – cependant tous les modernes compilateur permet arbitraire long identifiants, donc, dans notre temps (le 21e siècle), nous ne devrions pas avoir à vous soucier de cela.

Mais que faire si vous êtes confronté à un certain nombre de bibliothèques de qui vous ne pouvez pas modifier les noms de symbole / idenfiers? Peut-être que vous avez obtenu seulement un binaire statique et les en-têtes ou ne veulent pas, ou ne sont pas autorisés à régler et recompiler vous-même.

127voto

datenwolf Points 85093

Au moins dans le cas de la statique des bibliothèques, vous pouvez le contourner assez facilement.

Tenir compte de ces en-têtes des bibliothèques de foo et bar. Pour l'amour de ce tutoriel, je vais aussi vous donner les fichiers source

exemples/ex01/foo.h

int spam(void);
double eggs(void);

exemples/ex01/foo.c (ce qui peut être opaque/non disponible)

int the_spams;
double the_eggs;

int spam()
{
    return the_spams++;
}

double eggs()
{
    return the_eggs--;
}

exemple/ex01/bar.h

int spam(int new_spams);
double eggs(double new_eggs);

exemples/ex01/bar.c (ce qui peut être opaque/non disponible)

int the_spams;
double the_eggs;

int spam(int new_spams)
{
    int old_spams = the_spams;
    the_spams = new_spams;
    return old_spams;
}

double eggs(double new_eggs)
{
    double old_eggs = the_eggs;
    the_eggs = new_eggs;
    return old_eggs;
}

Nous voulons utiliser ces dans un programme de foobar

exemple/ex01/foobar.c

#include <stdio.h>

#include "foo.h"
#include "bar.h"

int main()
{
    const int    new_bar_spam = 3;
    const double new_bar_eggs = 5.0f;

    printf("foo: spam = %d, eggs = %f\n", spam(), eggs() );
    printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n", 
            spam(new_bar_spam), new_bar_spam, 
            eggs(new_bar_eggs), new_bar_eggs );

    return 0;
}

Un problème apparaît immédiatement: C ne connaît pas la surcharge. Nous avons donc deux fois deux fonctions avec nom identique mais de signature différente. Nous avons donc besoin d'un moyen pour distinguer ceux-ci. De toute façon, permet de voir ce qu'est un compilateur a à dire à ce sujet:

example/ex01/ $ make
cc    -c -o foobar.o foobar.c
In file included from foobar.c:4:
bar.h:1: error: conflicting types for ‘spam'
foo.h:1: note: previous declaration of ‘spam' was here
bar.h:2: error: conflicting types for ‘eggs'
foo.h:2: note: previous declaration of ‘eggs' was here
foobar.c: In function ‘main':
foobar.c:11: error: too few arguments to function ‘spam'
foobar.c:11: error: too few arguments to function ‘eggs'
make: *** [foobar.o] Error 1

Bon, ce n'était pas une surprise, il m'a seulement dit de nous, ce que nous savions déjà, ou au moins soupçonné.

Si nous pouvons en quelque sorte résoudre que l'identifiant de collision sans modifier l'original des bibliothèques le code source ou les en-têtes? En fait, nous pouvons.

D'abord, permet de régler le temps de compilation des questions. Pour cela, nous avons entourent l'en-tête comprend un tas de préprocesseur #define directives du préfixe tous les symboles exportés par la bibliothèque. Plus tard, nous le faisons avec quelques agréables wrapper-d'en-tête, mais juste pour le plaisir de démontrer ce qu'il se passe sur le faisaient mot à mot dans l' foobar.c fichier source:

exemple/ex02/foobar.c

#include <stdio.h>

#define spam foo_spam
#define eggs foo_eggs
#  include "foo.h"
#undef spam
#undef eggs

#define spam bar_spam
#define eggs bar_eggs
#  include "bar.h"
#undef spam
#undef eggs

int main()
{
    const int    new_bar_spam = 3;
    const double new_bar_eggs = 5.0f;

    printf("foo: spam = %d, eggs = %f\n", foo_spam(), foo_eggs() );
    printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n", 
           bar_spam(new_bar_spam), new_bar_spam, 
           bar_eggs(new_bar_eggs), new_bar_eggs );

    return 0;
}

Maintenant, si nous compilons ce...

example/ex02/ $ make
cc    -c -o foobar.o foobar.c
cc   foobar.o foo.o bar.o   -o foobar
bar.o: In function `spam':
bar.c:(.text+0x0): multiple definition of `spam'
foo.o:foo.c:(.text+0x0): first defined here
bar.o: In function `eggs':
bar.c:(.text+0x1e): multiple definition of `eggs'
foo.o:foo.c:(.text+0x19): first defined here
foobar.o: In function `main':
foobar.c:(.text+0x1e): undefined reference to `foo_eggs'
foobar.c:(.text+0x28): undefined reference to `foo_spam'
foobar.c:(.text+0x4d): undefined reference to `bar_eggs'
foobar.c:(.text+0x5c): undefined reference to `bar_spam'
collect2: ld returned 1 exit status
make: *** [foobar] Error 1

... d'abord il semble que les choses ont empiré. Mais regardez de plus près: en Fait, la phase de compilation est allé très bien. C'est juste l'éditeur de liens qui se plaignent aujourd'hui qu'il y a des symboles de la collision et il nous raconte l'emplacement (fichier source et ligne) où cela se produit. Et comme on peut le voir ces symboles sont unprefixed.

Nous allons prendre un regard sur les tables de symboles avec la nm utilitaire:

example/ex02/ $ nm foo.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams

example/ex02/ $ nm bar.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams

Alors maintenant, nous avons des problèmes avec l'exercice de préfixe de ces symboles dans certains binaire opaque. Oui, je sais dans le cadre de cet exemple, nous avons les sources et la possibilité de changer cela. Mais pour l'instant, il suffit de supposer vous avez seulement ceux .o fichiers, ou un .un (qui est en fait juste un tas de .o).

objcopy à la rescousse

Il est un outil particulièrement intéressant pour nous: objcopy

objcopy travaille sur des fichiers temporaires, de sorte que nous pouvons l'utiliser comme s'il s'agissait d'exploitation en place. Il y en a un option/opération appelée" --prefix-symboles et vous avez 3 devine ce qu'il fait.

Donc, nous allons jeter ce gars sur notre têtu bibliothèques:

example/ex03/ $ objcopy --prefix-symbols=foo_ foo.o
example/ex03/ $ objcopy --prefix-symbols=bar_ bar.o

nm nous montre que cela semblait fonctionner:

example/ex03/ $ nm foo.o
0000000000000019 T foo_eggs
0000000000000000 T foo_spam
0000000000000008 C foo_the_eggs
0000000000000004 C foo_the_spams

example/ex03/ $ nm bar.o
000000000000001e T bar_eggs
0000000000000000 T bar_spam
0000000000000008 C bar_the_eggs
0000000000000004 C bar_the_spams

Permet d'essayer de lier ce truc:

example/ex03/ $ make
cc   foobar.o foo.o bar.o   -o foobar

Et en effet, il a travaillé:

example/ex03/ $ ./foobar 
foo: spam = 0, eggs = 0.000000
bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000

Maintenant, je laisse en exercice au lecteur de mettre en œuvre un outil ou un script qui extrait automatiquement les symboles d'une bibliothèque à l'aide nm, écrit un wrapper fichier d'en-tête de la structure

/* wrapper header wrapper_foo.h for foo.h */
#define spam foo_spam
#define eggs foo_eggs
/* ... */
#include <foo.h>
#undef spam
#undef eggs
/* ... */

et applique le symbole du préfixe à la bibliothèque statique de l'objet de fichiers à l'aide de objcopy.

Quid des bibliothèques partagées?

En principe, la même chose peut être fait avec les bibliothèques partagées. Cependant bibliothèques partagées, le nom l'indique, sont partagés entre de multiples programmes, afin de jouer avec une bibliothèque partagée de cette façon n'est pas une bonne idée.

Vous n'obtiendrez pas autour de l'écriture d'un trampoline wrapper. Pire encore vous ne pouvez pas lier à l'encontre de la bibliothèque partagée sur le fichier d'objet de niveau, mais sont contraints de faire le chargement dynamique. Mais cela mérite son propre article.

Restez à l'écoute, et bon codage.

7voto

R.. Points 93718

Les règles de la norme C de mettre des contraintes sur les personnes (pour la sécurité de la compilation): Un compilateur C peut voir que les 8 premiers caractères d'un identifiant, donc foobar2k_eggs et foobar2k_spam peut être interprété comme les mêmes identifiants valablement – cependant tous les modernes compilateur permet arbitraire long identifiants, donc, dans notre temps (le 21e siècle), nous ne devrions pas avoir à vous soucier de cela.

Ce n'est pas une simple extension de la les compilateurs modernes; le courant C norme exige que le compilateur pour soutenir raisonnablement longue des noms externes. J'oublie la longueur exacte, mais c'est quelque chose de 31 caractères maintenant, si je me souviens bien.

Mais que faire si vous êtes confronté à un certain nombre de bibliothèques de qui vous ne pouvez pas modifier les noms de symbole / idenfiers? Peut-être que vous avez seulement un binaire statique et les en-têtes ou ne veulent pas, ou ne sont pas autorisés à régler et recompiler vous-même.

Puis vous êtes coincé. Se plaindre à l'auteur de la bibliothèque. Une fois, j'ai rencontré un bogue où les utilisateurs de mon application ont été incapables de construire sur Debian à cause de Debian libSDL reliant libsoundfile, ce qui (au moins à l'époque) a pollué l'espace de noms global horriblement avec des variables telles que l' dsp (je ne plaisante pas!). Je me suis plaint à Debian, et fixe leurs forfaits et envoyé le fixer en amont, où je suppose qu'il a été appliqué, car je n'ai jamais entendu de nouveau le problème.

Je pense vraiment que c'est la meilleure approche, car elle résout le problème pour tout le monde. Tout local hack vous ne quitterez le problème dans la bibliothèque pour le prochain utilisateur malheureux à rencontrer et battre de nouveau.

Si vous avez vraiment besoin d'une solution rapide, et vous avez de la source, vous pouvez ajouter un tas d' -Dfoo=crappylib_foo -Dbar=crappylib_bar etc. pour le makefile pour le fixer. Sinon, utilisez l' objcopy solution que vous avez trouvé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X