2017-07-11 1 views
17

Cet exemple se compose de plusieurs fichiers:Force un agent de liaison à l'échec d'une erreur multiple de définition, même si, y compris --whole-archives

// baz.cxx 
int wat = 0; 
int counter = ++wat; 
// foo.cxx (empty) 
// bar.cxx (empty) 
// main.cxx 
#include <iostream> 
extern int wat; 
int main() { 
    std::cout << wat << '\n'; 
} 
// makefile 
run : main.cxx foo.so bar.so 
    g++ -std=c++11 $^ -o [email protected] 

baz.a : baz.cxx 
    g++ -std=c++11 -c $^ -o baz.o -fPIC 
    ar rcs [email protected] baz.o 

%.so : %.cxx baz.a 
    g++ -std=c++11 $< -Wl,--whole-archive baz.a -Wl,--no-whole-archive -o [email protected] -shared -fPIC 

En l'état, si vous exécutez simplement make && LD_LIBRARY_PATH=. ./run, tout avec compiler, construire, lier, exécuter et afficher 2. En effet, les deux foo.so et bar.so fournissent wat et l'initialisation pour counter est exécutée deux fois.

Y at-il un moyen de forcer en quelque sorte run à ne pas faire le lien avec une erreur multiple de définition dans ce cas, tout en veillant à ce que les deux foo.so et bar.so ont une définition pour wat?

+0

Pas vraiment une solution mais peut-être pourriez-vous mettre le code d'initialisation dans un constructeur pour une classe statique qui vérifie si elle a déjà été initialisée et ne l'initialise pas à nouveau? – Curious

+0

@Curious Ouais, c'est une option. Cela semble plutôt affreux cependant et j'aimerais vraiment beaucoup ne pas le faire. Le laisser en dernier recours. – Barry

+0

La sortie est '1', pour moi, avec g ++ 4.9.2, 5.4.1, 6.2.0, 6.3.0, 7.0.1, GNU ld 2.26.1, 2.28, ubuntu 16.04, 17.04. –

Répondre

2

Vous pouvez utiliser des scripts de liens libfoo.so et libbar.so avec une partie statique qui crée un conflit de symboles (et installer les DSO réels dans un endroit non évidente, de sorte que -lfoo et -lbar ne les ramassent pas).

Quelque chose comme ceci:

  • libfoo.so

    INPUT(conflict.o) 
    INPUT(./foo.so) 
    
  • libbar.so

    INPUT(conflict.o) 
    INPUT(./bar.so) 
    
  • conflict.cxx

    int conflict; 
    

Il en résulte:

g++ -std=c++11 main.cxx -o run -L. -lfoo -lbar 
conflict.o:(.bss+0x0): multiple definition of `conflict' 
conflict.o:(.bss+0x0): first defined here 
collect2: error: ld returned 1 exit status 

Cela ne détecte des conflits au sein de la même série de l'éditeur de liens. Vous pouvez toujours lier deux DSO différents avec -lfoo et -lbar, et lier ces deux dans l'application, sans une erreur de l'éditeur de lien.

Un problème similaire est résolu par les annotations ABI, mais celles-ci nécessitent des modifications spécifiques ld, je pense. Si un contrôle d'exécution est acceptable, vous pouvez avoir une liste de symboles faibles définis par chaque DSO qui doit être en conflit et implémenter un constructeur ELF qui annule le processus si plusieurs d'entre eux sont définis. Je ne suis pas au courant de quoi que ce soit qui réalise quelque chose de tout aussi fiable au moment du lien statique avec la chaîne d'outils GNU. Peut-être que cela vaut un bug RFE contre binutils.

+0

Espérait quelque chose d'un peu plus agréable que cela, mais ça va faire. À votre santé! – Barry

1

Je pense que la seule bonne façon de résoudre le problème est de déplacer le contenu de la solution bar.a vers une bibliothèque partagée et de résoudre ainsi le problème d'héritage de diamant ... mais je pense que c'est hors de portée de ce problème.

La seule chose que je peux penser pour vous est de créer un validateur "personnalisé" exécuté juste avant de lier l'exécutable final. Stg comme ceci:

nm *.so | \ # get all symbols 
    grep " [B|T] " | \ # only exported ones 
    egrep -v "__bss_start|_end|_fini|_init" | \ # filter out some commons, probably to be extended 
    awk '{print $3}' | \ # get only symbol name 
    sort | uniq -c | \ # sort and count 
    egrep -v "^[ ]+1 " # get only those that have multiple definitions 

Ceci imprime tous les symboles forts (exportés) des bibliothèques qui sont définies plus d'une fois. Vous pouvez facilement l'intégrer dans un script qui renvoie un code d'état d'erreur si la sortie n'est pas vide avec un message significatif.

Version expérimentale de votre patché makefile ressemblerait à ceci:

run: main.cxx foo.so bar.so 
    ! nm foo.so bar.so | grep " [B|T] " | egrep -v "__bss_start|_end|_fini|_init" | awk '{print $3}' | sort | uniq -c | egrep -v "^[ ]+1 " 
    g++ -std=c++11 $^ -o [email protected] 

(notez ! pour inverser le code de sortie de finale grep qui recherche une sortie uniq -c qui ne commence pas avec nue 1)

Je sais que c'est vraiment une solution hackish, moche et non-portable mais je pensais que cela pourrait être d'une certaine valeur dans les cas de coin comme le vôtre.