Segfault при объявлении переменной типа vector <shared_ptr <int>>

Код

Вот программа, которая дает segfault.

#include <iostream>
#include <vector>
#include <memory>

int main() 
{
    std::cout << "Hello World" << std::endl;

    std::vector<std::shared_ptr<int>> y {};  

    std::cout << "Hello World" << std::endl;
}

Конечно, в самой программе нет абсолютно ничего плохого. Основная причина segfault зависит от среды, в которой она построена и работает.


Фон

Мы, в Amazon, используем систему сборки, которая строит и разворачивает двоичные файлы (lib и bin) почти машинным образом. В нашем случае это означает, что он развертывает исполняемый файл (построенный из вышеуказанной программы) в $project_dir/build/bin/ и почти все его зависимости (то есть разделяемые библиотеки) в $project_dir/build/lib/. Почему я использовал фразу "почти", потому что для разделяемых библиотек таких libc.so, libm.so, ld-linux-x86-64.so.2 и, возможно, нескольких других, исполняемый файл выбирает из системы (т.е. От /lib64). Обратите внимание, что предполагается выбрать libstdc++ из $project_dir/build/lib.

Теперь я запускаю его следующим образом:

$ LD_LIBRARY_PATH=$project_dir/build/lib ./build/bin/run

segmentation fault

Однако, если я запустил его, не установив LD_LIBRARY_PATH. Это нормально.


Диагностика

1. LDD

Ниже приведены данные ldd для обоих случаев (обратите внимание, что я отредактировал вывод, чтобы упомянуть полную версию библиотек там, где есть разница)

$ LD_LIBRARY_PATH=$project_dir/build/lib ldd ./build/bin/run

linux-vdso.so.1 =>  (0x00007ffce19ca000)
libstdc++.so.6 => $project_dir/build/lib/libstdc++.so.6.0.20 
libgcc_s.so.1 =>  $project_dir/build/lib/libgcc_s.so.1 
libc.so.6 => /lib64/libc.so.6 
libm.so.6 => /lib64/libm.so.6 
/lib64/ld-linux-x86-64.so.2 (0x0000562ec51bc000)

и без LD_LIBRARY_PATH:

$ ldd ./build/bin/run

linux-vdso.so.1 =>  (0x00007fffcedde000)
libstdc++.so.6 => /usr/lib64/libstdc++.so.6.0.16 
libgcc_s.so.1 => /lib64/libgcc_s-4.4.6-20110824.so.1
libc.so.6 => /lib64/libc.so.6 
libm.so.6 => /lib64/libm.so.6 
/lib64/ld-linux-x86-64.so.2 (0x0000560caff38000)

2. gdb при segfaults

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7dea45c in _dl_fixup () from /lib64/ld-linux-x86-64.so.2
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.209.62.al12.x86_64
(gdb) bt
#0  0x00007ffff7dea45c in _dl_fixup () from /lib64/ld-linux-x86-64.so.2
#1  0x00007ffff7df0c55 in _dl_runtime_resolve () from /lib64/ld-linux-x86-64.so.2
#2  0x00007ffff7b1dc41 in std::locale::_S_initialize() () from $project_dir/build/lib/libstdc++.so.6
#3  0x00007ffff7b1dc85 in std::locale::locale() () from $project_dir/build/lib/libstdc++.so.6
#4  0x00007ffff7b1a574 in std::ios_base::Init::Init() () from $project_dir/build/lib/libstdc++.so.6
#5  0x0000000000400fde in _GLOBAL__sub_I_main () at $project_dir/build/gcc-4.9.4/include/c++/4.9.4/iostream:74
#6  0x00000000004012ed in __libc_csu_init ()
#7  0x00007ffff7518cb0 in __libc_start_main () from /lib64/libc.so.6
#8  0x0000000000401021 in _start ()
(gdb)

3. LD_DEBUG = все

Я также попытался увидеть информацию компоновщика, включив LD_DEBUG=all для случая segfault. Я нашел что-то подозрительное, поскольку он ищет символ pthread_once, и когда он не может найти это, он дает segfault (это моя интерпретация следующего выходного фрагмента BTW):

initialize program: $project_dir/build/bin/run

symbol=_ZNSt8ios_base4InitC1Ev;  lookup in file=$project_dir/build/bin/run [0]
symbol=_ZNSt8ios_base4InitC1Ev;  lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
binding file $project_dir/build/bin/run [0] to $project_dir/build/lib/libstdc++.so.6 [0]: normal symbol `_ZNSt8ios_base4InitC1Ev' [GLIBCXX_3.4]
symbol=_ZNSt6localeC1Ev;  lookup in file=$project_dir/build/bin/run [0]
symbol=_ZNSt6localeC1Ev;  lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
binding file $project_dir/build/lib/libstdc++.so.6 [0] to $project_dir/build/lib/libstdc++.so.6 [0]: normal symbol `_ZNSt6localeC1Ev' [GLIBCXX_3.4]
symbol=pthread_once;  lookup in file=$project_dir/build/bin/run [0]
symbol=pthread_once;  lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
symbol=pthread_once;  lookup in file=$project_dir/build/lib/libgcc_s.so.1 [0]
symbol=pthread_once;  lookup in file=/lib64/libc.so.6 [0]
symbol=pthread_once;  lookup in file=/lib64/libm.so.6 [0]
symbol=pthread_once;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]

Но я не вижу никаких pthread_once для случая, когда он работает успешно!


Вопросы

Я знаю, что его очень трудно отлаживать, как это, и, вероятно, я не дал много информации об окружающей среде и обо всем. Но, тем не менее, мой вопрос: что может быть возможной причиной этого segfault? Как отлаживать дальше и находить это? Как только я нахожу проблему, исправить будет легко.


Компилятор и платформа

Я использую GCC 4.9 на RHEL5.


Эксперименты

E # 1

Если я прокомментирую следующую строку:

std::vector<std::shared_ptr<int>> y {}; 

Он компилируется и работает нормально!

E # 2

Я просто включил в свою программу следующий заголовок:

#include <boost/filesystem.hpp>

и соответственно связаны. Теперь он работает без какого-либо segfault. Таким образом, кажется, что он зависит от libboost_system.so.1.53.0., некоторые требования выполняются или проблема обходится!

E # 3

Поскольку я видел, как он работал, когда я сделал исполняемый файл связанным с libboost_system.so.1.53.0, поэтому я сделал следующие шаги шаг за шагом.

Вместо использования #include <boost/filesystem.hpp> в самом коде я использую исходный код и запускаю его путем предварительной загрузки libboost_system.so с помощью LD_PRELOAD следующим образом:

$ LD_PRELOAD=$project_dir/build/lib/libboost_system.so $project_dir/build/bin/run

и он успешно работает!

Далее я сделал ldd на libboost_system.so, который дал список libs, два из которых были:

  /lib64/librt.so.1
  /lib64/libpthread.so.0

Поэтому вместо предварительной загрузки libboost_system я предварительно загружаю librt и libpthread отдельно:

$ LD_PRELOAD=/lib64/librt.so.1 $project_dir/build/bin/run

$ LD_PRELOAD=/lib64/libpthread.so.0 $project_dir/build/bin/run

В обоих случаях он успешно работал.

Теперь я пришел к выводу, что, загружая либо librt, либо libpthread (или оба), выполняются некоторые требования или обход проблемы! Тем не менее, я до сих пор не знаю причину проблемы.


Параметры компоновки и компоновки

Так как система сборки сложна и есть множество опций, которые есть по умолчанию. Поэтому я попытался явно добавить -lpthread с помощью команды CMake set, тогда она сработала, поскольку мы уже видели, что при предварительной загрузке libpthread она работает!

Чтобы увидеть разницу в построении между этими двумя случаями (когда это работает и когда-это-дает-segfault), я построил его в подробном режиме, передав -v в GCC, чтобы увидеть этапы компиляции и параметры, которые он фактически передает на cc1plus (компилятор) и collect2 (компоновщик).

(Обратите внимание, что пути были отредактированы для краткости, используя знаки доллара и фиктивные пути.)

$/gcc-4.9.4/cc1plus -quiet -v -I/a/include -I/b/include -iprefix $/gcc-4.9.4/-MMD main.cpp.d -MF main.cpp.o.d -MT main.cpp.o -D_GNU_SOURCE -D_REENTRANT -D __USE_XOPEN2K8 -D _LARGEFILE_SOURCE -D_FILE_OFFSET_BITS = 64 -D __STDC_FORMAT_MACROS -D __STDC_LIMIT_MACROS -D NDEBUG $/lab/main.cpp -quiet -dumpbase main.cpp -msse -mfpmath = sse -march = core2 -auxbase -strip main.cpp.o -g -O3 -Wall -Wextra -std = gnu ++ 1y -version -fdiagnostics-color = auto -ftemplate-depth = 128 -fno-operator-names -o/tmp/ccxfkRyd.s

Независимо от того, работает ли это или нет, аргументы командной строки cc1plus точно совпадают. Никакой разницы. Это не очень полезно.

Однако разница в времени соединения. Вот что я вижу, для случая, когда он работает:

$/gcc-4.9.4/collect2 -plugin $/gcc-4.9.4/liblto_plugin.so
-plugin-opt = $/gcc-4.9.4/lto-wrapper -plugin-opt = -fresolution =/tmp/cchl8RtI.res -plugin-opt = -pass-through = -lgcc_s -plugin-opt = -pass- через = -lgcc -plugin-opt = -pass-through = -lpthread -plugin-opt = -pass-through = -lc -plugin-opt = -pass-through = -lgcc_s -plugin-opt = -pass-through = -lgcc -eh-frame-hdr -m elf_x86_64 -export-dynamic -dynamic-linker/lib64/ld-linux-x86-64.so.2 -o запустить /usr/lib/../lib64/crt1.o /usr/lib/../lib64/crti.o $/gcc-4.9.4/crtbegin.o -L/a/lib -L/b/lib -L/с/Библиотека -lpthread --as необходимый main.cpp.o -lboost_timer -lboost_wave -lboost_chrono -lboost_filesystem -lboost_graph -lboost_locale -lboost_thread -lboost_wserialization -lboost_atomic -lboost_context -lboost_date_time -lboost_iostreams -lboost_math_c99 -lboost_math_c99f -lboost_math_c99l - lboost_math_tr1 -lboost_math_tr1f -lboost_math_tr1l -lboost_mpi -lboost_prg_exec_monitor -lboost_program_options -lboost_random -lboost_regex -lboost_serialization -lboost_signals -lboost_system -lboost_unit_test_framework -lboost_exception -lboost_test_exec_monitor -lbz2 -licui18n -licuuc -licudata -lz -rpath/а/Библиотека:/б/Библиотека:/c/lib: -lstdС++ -lm -lgcc_s -lgcc -lpthread -lc -lgcc_s -lgcc $/gcc-4.9.4/crtend.o/usr/lib/../lib64/crtn. о

Как вы можете видеть, -lpthread упоминается дважды! Первый -lpthread (за которым следует --as-needed) отсутствует для случая, когда он дает segfault. Это единственное различие между этими двумя случаями.


Вывод nm -C в обоих случаях

Интересно, что вывод nm -C в обоих случаях идентичен (если вы игнорируете целочисленные значения в первых столбцах).

0000000000402580 d _DYNAMIC
0000000000402798 d _GLOBAL_OFFSET_TABLE_
0000000000401000 t _GLOBAL__sub_I_main
0000000000401358 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
                 U _Unwind_Resume
0000000000401150 W std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_destroy()
0000000000401170 W std::vector<std::shared_ptr<int>, std::allocator<std::shared_ptr<int> > >::~vector()
0000000000401170 W std::vector<std::shared_ptr<int>, std::allocator<std::shared_ptr<int> > >::~vector()
0000000000401250 W std::vector<std::unique_ptr<int, std::default_delete<int> >, std::allocator<std::unique_ptr<int, std::default_delete<int> > > >::~vector()
0000000000401250 W std::vector<std::unique_ptr<int, std::default_delete<int> >, std::allocator<std::unique_ptr<int, std::default_delete<int> > > >::~vector()
                 U std::ios_base::Init::Init()
                 U std::ios_base::Init::~Init()
0000000000402880 B std::cout
                 U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000402841 b std::__ioinit
                 U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
                 U operator delete(void*)
                 U operator new(unsigned long)
0000000000401510 r __FRAME_END__
0000000000402818 d __JCR_END__
0000000000402818 d __JCR_LIST__
0000000000402820 d __TMC_END__
0000000000402820 d __TMC_LIST__
0000000000402838 A __bss_start
                 U __cxa_atexit
0000000000402808 D __data_start
0000000000401100 t __do_global_dtors_aux
0000000000402820 t __do_global_dtors_aux_fini_array_entry
0000000000402810 d __dso_handle
0000000000402828 t __frame_dummy_init_array_entry
                 w __gmon_start__
                 U __gxx_personality_v0
0000000000402838 t __init_array_end
0000000000402828 t __init_array_start
00000000004012b0 T __libc_csu_fini
00000000004012c0 T __libc_csu_init
                 U __libc_start_main
                 w __pthread_key_create
0000000000402838 A _edata
0000000000402990 A _end
000000000040134c T _fini
0000000000400e68 T _init
0000000000401028 T _start
0000000000401054 t call_gmon_start
0000000000402840 b completed.6661
0000000000402808 W data_start
0000000000401080 t deregister_tm_clones
0000000000401120 t frame_dummy
0000000000400f40 T main
00000000004010c0 t register_tm_clones
0000000000402580 d _DYNAMIC
0000000000402798 d _GLOBAL_OFFSET_TABLE_
0000000000401000 t _GLOBAL__sub_I_main
0000000000401358 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
                 U _Unwind_Resume
0000000000401150 W std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_destroy()
0000000000401170 W std::vector<std::shared_ptr<int>, std::allocator<std::shared_ptr<int> > >::~vector()
0000000000401170 W std::vector<std::shared_ptr<int>, std::allocator<std::shared_ptr<int> > >::~vector()
0000000000401250 W std::vector<std::unique_ptr<int, std::default_delete<int> >, std::allocator<std::unique_ptr<int, std::default_delete<int> > > >::~vector()
0000000000401250 W std::vector<std::unique_ptr<int, std::default_delete<int> >, std::allocator<std::unique_ptr<int, std::default_delete<int> > > >::~vector()
                 U std::ios_base::Init::Init()
                 U std::ios_base::Init::~Init()
0000000000402880 B std::cout
                 U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000402841 b std::__ioinit
                 U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
                 U operator delete(void*)
                 U operator new(unsigned long)
0000000000401510 r __FRAME_END__
0000000000402818 d __JCR_END__
0000000000402818 d __JCR_LIST__
0000000000402820 d __TMC_END__
0000000000402820 d __TMC_LIST__
0000000000402838 A __bss_start
                 U __cxa_atexit
0000000000402808 D __data_start
0000000000401100 t __do_global_dtors_aux
0000000000402820 t __do_global_dtors_aux_fini_array_entry
0000000000402810 d __dso_handle
0000000000402828 t __frame_dummy_init_array_entry
                 w __gmon_start__
                 U __gxx_personality_v0
0000000000402838 t __init_array_end
0000000000402828 t __init_array_start
00000000004012b0 T __libc_csu_fini
00000000004012c0 T __libc_csu_init
                 U __libc_start_main
                 w __pthread_key_create
0000000000402838 A _edata
0000000000402990 A _end
000000000040134c T _fini
0000000000400e68 T _init
0000000000401028 T _start
0000000000401054 t call_gmon_start
0000000000402840 b completed.6661
0000000000402808 W data_start
0000000000401080 t deregister_tm_clones
0000000000401120 t frame_dummy
0000000000400f40 T main
00000000004010c0 t register_tm_clones

Ответ 1

Учитывая момент сбоя и тот факт, что предварительная загрузка libpthread, кажется, исправляет его, я считаю, что выполнение двух случаев расходится на locale_init.cc:315. Вот выдержка из кода:

  void
  locale::_S_initialize()
  {
#ifdef __GTHREADS
    if (__gthread_active_p())
      __gthread_once(&_S_once, _S_initialize_once);
#endif
    if (!_S_classic)
      _S_initialize_once();
  }

__gthread_active_p() возвращает true, если ваша программа связана с pthread, в частности, она проверяет, доступен ли pthread_key_create. В моей системе этот символ определен в "/usr/include/c++/7.2.0/x86_64-pc-linux-gnu/bits/gthr-default.h" как static inline, следовательно, это потенциальный источник ODR нарушение.

Обратите внимание, что LD_PRELOAD=libpthread,so всегда вызывает __gthread_active_p() для возврата true.

__gthread_once - это еще один встроенный символ, который всегда должен пересылаться на pthread_once.

Трудно догадаться, что происходит без отладки, но я подозреваю, что вы попадаете в истинную ветвь __gthread_active_p(), даже если это не так, и программа затем сработает, потому что нет pthread_once для вызова.

ИЗМЕНИТЬ: Поэтому я сделал несколько экспериментов, единственный способ, с помощью которого я могу получить сбой в std::locale::_S_initialize, - это если __gthread_active_p возвращает true, но pthread_once не связан.

libstdС++ не ссылается непосредственно на pthread, но импортирует половину pthread_xx в качестве слабых объектов, что означает, что они могут быть undefined и не вызывать ошибки компоновщика.

Очевидно, что связывание pthread приведет к исчезновению сбоя, но если я прав, основная проблема заключается в том, что ваш libstdc++ считает, что он находится в многопоточном исполняемом файле, даже если мы не связали pthread in.

Теперь __gthread_active_p использует __pthread_key_create, чтобы решить, есть ли у нас потоки или нет. Это определено в вашем исполняемом файле как слабый объект (может быть nullptr и все еще быть в порядке). Я на 99% уверен, что символ существует из-за shared_ptr (удалите его и снова проверьте nm). Итак, как-то __pthread_key_create привязывается к действительному адресу, возможно, из-за последнего -lpthread в ваших флагах компоновщика. Вы можете проверить эту теорию, поставив точку останова на locale_init.cc:315 и проверив, какую ветвь вы берете.

EDIT2

Сводка комментариев, проблема только воспроизводима, если у нас есть все следующее:

  • Используйте ld.gold вместо ld.bfd
  • Используйте --as-needed
  • Принудительное определение __pthread_key_create, в данном случае с помощью экземпляра std::shared_ptr.
  • Не ссылается на pthread или связывает pthread после --as-needed.

Чтобы ответить на вопросы в комментариях:

Почему он использует золото по умолчанию?

По умолчанию используется /usr/bin/ld, который на большинстве дистрибутивов является символической ссылкой на /usr/bin/ld.bfd или /usr/bin/ld.gold. Такое умолчание можно использовать с помощью update-alternatives. Я не уверен, почему в вашем случае это ld.gold, насколько я понимаю, RHEL5 поставляется с ld.bfd по умолчанию.

И почему золото не добавляет зависимость pthread.so к бинарному, если это необходимо?

Потому что определение того, что нужно, как-то теневое. man ld говорит (внимание мое):

- по мере необходимости

<Не p > - не-по мере необходимости

Этот параметр влияет на теги ELF DT_NEEDED для динамических библиотек, упомянутых в командной строке после опции -as.Обычно компоновщик добавляет DT_NEEDED            тег для каждой динамической библиотеки, упомянутой в командной строке, независимо от того, нужна ли библиотека или нет. --s-needed вызывает тег DT_NEEDED для            испускаются только для библиотеки, которая в этой точке ссылки удовлетворяет не слабой undefined символу с регулярного объектный файл или, если библиотека не            найденные в списках DT_NEEDED других необходимых библиотек, не-слабая ссылка символа undefined из другой необходимой динамической библиотека. Объектные файлы или библиотеки            появляется в командной строке после того, как соответствующая библиотека не влияет на то, просматривается ли библиотека по мере необходимости. Это похоже к правилам извлечения            объектные файлы из архивов. --no-as-needed восстанавливает поведение по умолчанию.

Теперь, согласно этот отчет об ошибках, gold выполняет роль "не слабая часть undefined", а ld.bfd видит слабые символы по мере необходимости. TBH У меня нет полного понимания этого, и есть некоторое обсуждение этой ссылки относительно того, следует ли считать это ошибкой ld.gold или ошибкой libstdc++.

Почему мне нужно указать -pthread и -lpthread как? (-pthread является пройденный по умолчанию нашей системой сборки, и я передаю -lpthread, чтобы сделать он работает с золотом).

-pthread и -lpthread делать разные вещи (см. pthread vs lpthread). По моему мнению, первое должно означать последнее.

Независимо от того, вы можете передать -lpthread только один раз, но вам нужно сделать это до --as-needed или использовать --no-as-needed после последней библиотеки и до -lpthread.

Также стоит отметить, что я не смог воспроизвести эту проблему в своей системе (GCC 7.2), даже используя золотой линкер. Поэтому я подозреваю, что он был исправлен в более поздней версии libstdС++, что также может объяснить, почему он не segfault, если вы используете стандартную библиотеку системы.

Ответ 2

Вероятно, это проблема, вызванная незначительными несоответствиями между libstdc++ ABI. GCC 4.9 не является системным компилятором на Red Hat Enterprise Linux 5, поэтому не совсем понятно, что вы там используете (DTS 3?).

Известно, что реализация локали довольно чувствительна к несоответствиям ABI. См. Эту тему в списке gcc-help:

Лучше всего выяснить, какие биты libstdc++ связаны между собой, и каким-то образом достичь согласованности (либо путем скрытия символов, либо перекомпиляции вещей, чтобы они были совместимы).

Также может быть полезно исследовать гибридную модель сцепления, используемую для libstdc++ в Red Hat Developer Toolset (где новые биты связаны статически, но основная часть стандартной библиотеки С++ использует существующую DSO системы), но система libstdc++ в Red Hat Enterprise Linux 5 может быть слишком стар для этого, если вам нужна поддержка существующих языковых функций.