Когда g++ статическая ссылка pthread, вызывает ошибку сегментации, почему?
#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;
}
Выше простой код C++.
компилировать с: g++ -pthread -o one one.cpp -Wall -std=c++11 -O3
ldd one
Готы:
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)
бежать ./one
, все отлично.
Затем я пытаюсь статическую ссылку: g++ -pthread -o one one.cpp -Wall -std=c++11 -O3 -static
ldd one
Готы:
not a dynamic executable
Но когда я запускаю его, что-то идет не так...
test start
Segmentation fault (core dumped)
пересобрать с -g
и GDB показывает:
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)
Почему это?
ОБНОВЛЕНИЕ==============================
с помощью boost::thread
библиотека (буст версия: 1.60),
замещать std::thread
с boost::thread
и сделать статическую ссылку,
g++ -pthread -o one1 one.cpp -Wall -std=c++11 -O3 -I /opt/boost/include/ -L /opt/boost/lib/ -lboost_system -lboost_thread -static
никаких проблем не возникало!
смущенный...
1 ответ
Во-первых, решение. Это здесь будет работать:
g++ -o one one.cpp -Wall -std=c++11 -O3 -static -pthread \
-Wl,--whole-archive -lpthread -Wl,--no-whole-archive
Когда вы используете -pthread
компилятор уже связывается с pthread (и в зависимости от платформы он определяет дополнительные макросы, такие как -D_REENTRANT
см. этот вопрос для более подробной информации).
Так что если -pthread
подразумевает -lpthread
зачем вам указывать -lpthread
когда вы статически связываете? И что Wl,--whole-archive
делать?
Понимание слабых символов
В Unix используется формат файла ELF, который имеет понятие слабых и сильных символов. Цитировать со страницы Википедии:
По умолчанию без каких-либо аннотаций символ в объектном файле является сильным. Во время связывания сильный символ может перекрывать слабый символ с тем же именем. Напротив, два сильных символа, которые имеют общее имя, приводят к ошибке ссылки во время соединения.
В динамических и статических библиотеках есть небольшая разница. В статических библиотеках компоновщик остановится на первом символе, даже если он слабый, и перестанет искать сильные. Чтобы заставить его смотреть на все символы (как это было бы для динамически связанной библиотеки), ld
поддерживает --whole-archive
вариант.
Цитировать из man ld
:
--whole-archive:
Для каждого архива, упомянутого в командной строке после параметра --whole-archive, включайте каждый объектный файл в архиве в ссылку, а не ищите в архиве требуемые объектные файлы. Обычно это используется для преобразования архивного файла в общую библиотеку, заставляя каждый объект включаться в результирующую общую библиотеку. Эта опция может использоваться более одного раза.
Далее объясняется, что из gcc вы должны передать опцию как -Wl,--whole-archive
:
Два замечания при использовании этой опции от gcc: во-первых, gcc не знает об этой опции, поэтому вы должны использовать -Wl,-whole-archive. Во-вторых, не забудьте использовать -Wl,-no-whole-archive после вашего списка архивов, потому что gcc добавит свой собственный список архивов в вашу ссылку, и вы можете не захотеть, чтобы этот флаг также влиял на них.
И это объясняет, как выключить это, снова:
--no-whole-archive:
Отключите эффект опции --whole-archive для последующих архивных файлов.
Слабые символы в pthread и libstdC++
Одним из вариантов использования слабых символов является возможность замены реализаций на оптимизированные. Другой заключается в использовании заглушек, которые можно впоследствии заменить при необходимости.
Например, fputc
( концептуально используется printf
) требуется POSIX, чтобы быть потокобезопасным и должен быть синхронизирован, что является дорогостоящим. В однопоточной среде вы не хотите оплачивать расходы. Поэтому реализация может реализовывать функции синхронизации как пустые заглушки и объявлять функции как слабые символы.
Позже, если многопоточная библиотека связана (например, pthread), становится очевидным, что поддержка однопоточности не предусмотрена. При связывании многопоточной библиотеки компоновщик может затем заменить заглушки на реальные функции синхронизации (определенные как сильные символы и реализованные библиотекой потоков). С другой стороны, если нет многопоточной библиотеки, исполняемый файл будет использовать заглушки для функции синхронизации.
Glibc (предоставление fputc
) и pthreads, кажется, используют именно этот трюк. За подробностями обращайтесь к этому вопросу об использовании слабых символов в glibc. Приведенный выше пример взят из этого ответа.
nm позволяет взглянуть на это подробно, что согласуется с приведенным выше ответом:
$ nm /usr/lib/libc.a 2>/dev/null | grep pthread_mutex_lock
w __pthread_mutex_lock
... (repeats)
"w" означает "слабый", поэтому статически связанная библиотека libc содержит __pthread_mutex_lock
как слабый символ. Статически связанная библиотека pthread содержит его как сильный символ:
$ 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
Вернуться к примеру программы
Глядя на зависимости разделяемой библиотеки динамически связанного исполняемого файла, я получаю почти такой же результат: ldd
на моей машине:
$ 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)
Распечатка вызовов библиотеки с помощью ltrace приводит к следующему выводу:
$ 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) +++
В качестве примера, std::thread::join
называется, который, скорее всего, будет использовать pthread_join
внутренне. Этот символ можно найти в (динамически связанных) библиотеках, перечисленных в ldd
выход, а именно в libstdc++.so.6
а также 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
В динамически связанном исполняемом файле компоновщик заменит слабые символы на сильные символы. В этом примере мы должны применить ту же семантику для статически связанных библиотек. Поэтому -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
нужно.
Выяснить это немного методом проб и ошибок. По крайней мере, я не нашел четкой документации по этому вопросу. Я предполагаю, что это связано с тем, что статическое линкование в Linux стало скорее крайним случаем, тогда как динамическое линкование часто является каноническим подходом к использованию библиотек (для сравнения см. Статическое линкование и динамическое линкование). Самый экстремальный пример, который я когда-либо видел и лично изо всех сил пытался заставить его работать, - это статическая связь TBB.
Приложение: Временное решение для Autotools
Если вы используете autotools в качестве системы сборки, вам нужен обходной путь, так как automake не позволяет вам устанавливать параметры в LDADD. К сожалению, вы не можете написать:
(Makefile.am)
mytarget_LDADD = -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
В качестве обходного пути вы можете избежать проверки, определив флаги в configure.ac и используя их следующим образом:
(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@
У меня была аналогичная проблема при связывании предварительно созданного C++ .a
архив, использующий pthread. В моем случае мне понадобилось помимо-Wl,--whole-archive -lpthread -Wl,--no-whole-archive
также делаю -Wl,-u,...
для каждого слабого символа.
Моим симптомом были сбои во время выполнения, и при использовании gdb для дизассемблирования я мог видеть, что сбой произошел сразу после callq 0x0
что казалось подозрительным. Сделал некоторый поиск и обнаружил, что другие видели это со статической связью pthread.
Я прикидываю, какие символы нужно разрешить, используя nm
и ищи w
символы. После связывания я мог видеть, чтоcallq 0x0
инструкции были обновлены различными адресами символов для pthread_mutex_lock
и т.п.