web-dev-qa-db-fra.com

quand le lien statique g ++ pthread, cause une erreur de segmentation, pourquoi?

#include <iostream>
#include <map>
#include <thread>

#define SIZE 1024
#define AMOUNT 100000
#define THREADS 4

class A
{
private:
    char a[SIZE];
};

void test()
{
    std::cout << "test start\n";
    std::map<int, A*> container;
    for(int i=0; i<AMOUNT; i++)
    {
        A* a = new A();
        std::pair<int, A*>p = std::make_pair(i, a);
        container.insert(p);
    }

    std::cout << "test release\n";
    for(int i=0; i<AMOUNT; i++)
    {
        auto iter = container.find(i);
        delete iter->second;
        container.erase(iter);
    }
    std::cout << "test end\n";
}

int main()
{
    std::thread ts[THREADS];
    for(int i=0; i<THREADS; i++)
    {
        ts[i] = std::thread(test);
    }

    for(std::thread& x: ts)
    {
        x.join();
    }

    return 0;
}

Ci-dessus est un simple code c ++.

compiler avec: g++ -pthread -o one one.cpp -Wall -std=c++11 -O3

ldd one, gots:

    linux-vdso.so.1 =>  (0x00007ffebafce000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb47352a000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb473313000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb4730f4000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb472d2a000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb472a22000)
    /lib64/ld-linux-x86-64.so.2 (0x00005654c5112000)

courir ./one, tout va bien.

Ensuite, j'essaye un lien statique: g++ -pthread -o one one.cpp -Wall -std=c++11 -O3 -static

ldd one, gots:

    not a dynamic executable

Mais quand je le lance, quelque chose ne va pas ...

test start
Segmentation fault (core dumped)

recompiler avec -g, et la gdb affiche:

wang[00:35][~/test]$ gdb one
GNU gdb (Ubuntu 7.10-1ubuntu2) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos Word" to search for commands related to "Word"...
Reading symbols from one...done.
(gdb) run
Starting program: /home/wang/test/one 
[Thread debugging using libthread_db enabled]
Using Host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7ffa700 (LWP 3623)]
test start
[New Thread 0x7ffff77f8700 (LWP 3624)]
test start
[New Thread 0x7ffff6ff7700 (LWP 3625)]
test start
[New Thread 0x7ffff67f6700 (LWP 3626)]
test start

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) 

Pourquoi ça ?

MISE À JOUR ============================ ==

en utilisant boost::thread bibliothèque (version boost: 1.60),

remplacer std::thread avec boost::thread, et créer un lien statique,

g++ -pthread -o one1 one.cpp -Wall -std=c++11 -O3 -I /opt/boost/include/ -L /opt/boost/lib/ -lboost_system -lboost_thread -static

aucun problème n'est survenu!

confus...

27
Yueyoum

Tout d'abord, la solution. Cela fonctionnera ici:

Mise à jour: Depuis Ubuntu 18.04 , vous devez également établir un lien avec librt (ajoutez -lrt):

g++ -o one one.cpp -Wall -std=c++11 -O3 -static -lrt -pthread \
    -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

(continuer avec la réponse originale)

g++ -o one one.cpp -Wall -std=c++11 -O3 -static -pthread \
    -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

Lorsque vous utilisez -pthread, le compilateur sera déjà lié à pthread (et selon la plate-forme, il définit des macros supplémentaires comme -D_REENTRANT, voir cette question pour plus de détails).

Donc si -pthread implique -lpthread, pourquoi avez-vous besoin de spécifier -lpthread lorsque vous établissez une liaison statique? Et qu'est-ce que Wl,--whole-archive faire?

Comprendre les symboles faibles

Sous Unix, le format de fichier ELF est utilisé, avec le concept symboles faibles et forts . Pour citer la page Wikipedia :

Par défaut, sans aucune annotation, un symbole dans un fichier objet est fort . Pendant la liaison, un symbole fort peut remplacer un symbole faible du même nom. En revanche, deux symboles forts qui partagent un nom génèrent une erreur de lien pendant le temps de liaison.

Il existe une différence subtile en ce qui concerne les bibliothèques dynamiques et statiques. Dans les bibliothèques statiques, l'éditeur de liens s'arrête au premier symbole, même s'il est faible, et arrête de rechercher des symboles forts. Pour le forcer à regarder tous les symboles (comme il l'aurait fait pour une bibliothèque liée dynamiquement), ld supporte le --whole-archive option.

Pour citer man ld :

--whole-archive: Pour chaque archive mentionnée sur la ligne de commande après l'option --whole-archive, incluez chaque fichier objet dans l'archive dans le lien, plutôt que de rechercher dans l'archive les fichiers objet requis. Ceci est normalement utilisé pour transformer un fichier d'archive en bibliothèque partagée, forçant chaque objet à être inclus dans la bibliothèque partagée résultante. Cette option peut être utilisée plusieurs fois.

Il continue en expliquant que depuis gcc, vous devez passer l'option comme -Wl,--whole-archive:

Deux remarques lors de l'utilisation de cette option de gcc: Tout d'abord, gcc ne connaît pas cette option, vous devez donc utiliser -Wl, -whole-archive. Deuxièmement, n'oubliez pas d'utiliser -Wl, -no-whole-archive après votre liste d'archives, car gcc ajoutera sa propre liste d'archives à votre lien et vous ne voudrez peut-être pas que cet indicateur les affecte également.

Et cela explique comment le désactiver, encore une fois:

--no-whole-archive: Désactivez l'effet de l'option --whole-archive pour les fichiers d'archive suivants.

Symboles faibles dans pthread et libstdc ++

L'un des cas d'utilisation des symboles faibles est de pouvoir échanger des implémentations avec des implémentations optimisées. Une autre consiste à utiliser des talons, qui peuvent ensuite être remplacés si nécessaire.

Par exemple, fputc ( tilisé de manière conceptuelle par printf ) est requis par POSIX pour être thread-safe et doit être synchronisé, ce qui est coûteux. Dans un environnement monothread, vous ne voulez pas payer les frais. Une implémentation pourrait donc implémenter les fonctions de synchronisation comme des stubs vides et déclarer les fonctions comme des symboles faibles.

Plus tard, si une bibliothèque multithread est liée (par exemple, pthread), il devient évident que la prise en charge d'un seul thread n'est pas prévue. Lors de la liaison de la bibliothèque multi-threading, l'éditeur de liens peut alors remplacer les stubs par les fonctions de synchronisation réelles (définies comme des symboles forts et implémentées par la bibliothèque de threading). En revanche, si aucune bibliothèque multithread n'est liée, l'exécutable utilisera les stubs pour la fonction de synchronisation.

glibc (fournissant fputc) et pthreads semblent utiliser exactement cette astuce. Pour plus de détails, reportez-vous à cette question sur l'utilisation des symboles faibles dans la glibc . L'exemple ci-dessus est tiré de cette réponse .

nm vous permet de le regarder en détail, ce qui semble cohérent avec la réponse citée ci-dessus:

$ nm /usr/lib/libc.a 2>/dev/null | grep pthread_mutex_lock
w __pthread_mutex_lock
... (repeats)

"w" signifie "faible", donc la bibliothèque libc liée statiquement contient __pthread_mutex_lock comme symbole faible. La bibliothèque pthread liée statiquement la contient comme un symbole fort:

$ nm /usr/lib/libpthread.a 2>/dev/null | grep pthread_mutex_lock
             U pthread_mutex_lock
pthread_mutex_lock.o:
00000000000006a0 T __pthread_mutex_lock
00000000000006a0 T pthread_mutex_lock
0000000000000000 t __pthread_mutex_lock_full

Retour au programme d'exemple

En regardant les dépendances de bibliothèque partagée de l'exécutable lié dynamiquement, j'obtiens presque la même sortie de ldd sur ma machine:

$ ldd one
linux-vdso.so.1 (0x00007fff79d6d000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007fcaaeeb3000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007fcaaeb9b000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fcaae983000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fcaae763000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fcaae3bb000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcaaf23b000)

L'impression des appels de bibliothèque avec ltrace , conduit à la sortie suivante:

$ ltrace -C ./one 
std::ios_base::Init::Init()(0x563ab8df71b1, 0x7ffdc483cae8, 0x7ffdc483caf8, 160) = 0
__cxa_atexit(0x7fab3023bc20, 0x563ab8df71b1, 0x563ab8df7090, 6)         = 0
operator new(unsigned long)(16, 0x7ffdc483cae8, 0x7ffdc483caf8, 192)    = 0x563ab918bc20
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80) = 0
operator new(unsigned long)(16, 0x7fab2f6a1fb0, 0, 0x800000)            = 0x563ab918bd70
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80) = 0
operator new(unsigned long)(16, 0x7fab2eea0fb0, 0, 0x800000)            = 0x563ab918bec0
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80test start
) = 0
operator new(unsigned long)(16, 0x7fab2e69ffb0, 0, 0x800000)            = 0x563ab918c010
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80test start
test start
) = 0
std::thread::join()(0x7ffdc483c9a0, 0x7fab2de9efb0, 0, 0x800000test start
test release
test release
test release
test release
test end
test end
test end
test end
)        = 0
std::thread::join()(0x7ffdc483c9a8, 0x7fab2eea19c0, 0x7fab2f6a2700, 0)  = 0
std::thread::join()(0x7ffdc483c9b0, 0x7fab2e6a09c0, 0x7fab2eea1700, 0)  = 0
std::thread::join()(0x7ffdc483c9b8, 0x7fab2de9f9c0, 0x7fab2e6a0700, 0)  = 0
+++ exited (status 0) +++

Par exemple, std::thread::join est appelé, qui utilisera très probablement pthread_join en interne. Ce symbole se trouve dans les bibliothèques (liées dynamiquement) répertoriées dans la sortie ldd, à savoir dans libstdc++.so.6 et libpthread.so.0:

$ nm /usr/lib/libstdc++.so.6 | grep pthread_join
                 w pthread_join

$ nm /usr/lib/libpthread.so.0 | grep pthread_join
0000000000008280 T pthread_join

Dans l'exécutable lié dynamiquement, l'éditeur de liens remplacera les symboles faibles par des symboles forts. Dans cet exemple, nous devons appliquer la même sémantique pour les bibliothèques liées statiquement. C'est pourquoi -Wl,--whole-archive -lpthread -Wl,--no-whole-archive est nécessaire.

Le découvrir est un peu d'essais et d'erreurs. Du moins, je n'ai trouvé aucune documentation claire à ce sujet. Je suppose que c'est parce que la liaison statique sur Linux est devenue plutôt un cas Edge , alors que la liaison dynamique est souvent l'approche canonique sur la façon d'utiliser les bibliothèques (pour une comparaison, voir Liaison statique vs liaison dynamique ). L'exemple le plus extrême que j'ai vu et que j'ai personnellement eu du mal à faire fonctionner est de lier TBB statiquement .

Annexe: solution de contournement pour les outils automatiques

Si vous utilisez les autotools comme système de construction, vous avez besoin d'une solution de contournement, car automake ne vous permet pas de définir des options dans dans LDADD. Malheureusement, vous ne pouvez pas écrire:

(Makefile.am)
mytarget_LDADD = -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

Pour contourner ce problème, vous pouvez éviter la vérification en définissant les indicateurs dans configure.ac et en les utilisant comme ceci:

(configure.ac)
WL_WHOLE_ARCHIVE_HACK="-Wl,--whole-archive"
WL_NO_WHOLE_ARCHIVE_HACK="-Wl,--no-whole-archive"
AC_SUBST(WL_WHOLE_ARCHIVE_HACK)
AC_SUBST(WL_NO_WHOLE_ARCHIVE_HACK)

(Makefile.am)
mytarget_LDADD = @WL_WHOLE_ARCHIVE_HACK@ -lpthread @WL_NO_WHOLE_ARCHIVE_HACK@
49
Philipp Claßen

J'ai eu un problème similaire lors de la liaison d'un C++ pré-construit .a archive utilisant pthread. Dans mon cas, j'avais besoin en plus de -Wl,--whole-archive -lpthread -Wl,--no-whole-archive font aussi -Wl,-u,... pour chaque symbole faible.

Mon symptôme était un plantage lors de l'exécution et lors de l'utilisation de gdb pour démonter, je pouvais voir que le plantage était juste après un callq 0x0 ce qui semblait suspect. A fait quelques recherches et a constaté que d'autres l'avaient vu avec un lien statique pthread.

Je détermine quels symboles forcer la résolution en utilisant nm et cherche les symboles w. Après la liaison, j'ai pu voir que le callq 0x0 les instructions ont été mises à jour avec diverses adresses de symboles en pthread_mutex_lock etc.

0
Mattias Wadman