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

код

вот программа, которая дает 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/ и почти все его зависимости (i.e общие библиотеки) в $project_dir/build/lib/. Почему я использовал фразу "практически" потому что для общих библиотек такие libc.so, libm.so, ld-linux-x86-64.so.2 и, возможно, несколько других, исполняемый файл выбирает из системы (i.e от /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 (компоновщик).

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

$/ГХК-4.9.4/cc1plus -тихо -в-я /в/включают -я /в/включают -iprefix $/ gcc-4.9.4 / - MMD main.СРР.D-MF main.СРР.о'.Д -Т основные.СРР.о -D_GNU_SOURCE-D_REENTRANT-D __ИСПОЛЬЗОВАТЬ_XOPEN2K8 -D _LARGEFILE_SOURCE -D _FILE_OFFSET_BITS=64-D __STDC_формат _ макросы - D __STDC_ограничение _ макросы-D NDEBUG $ / lab / main.cpp-quiet-dumpbase main.ЧГК -лаборатории -mfpmath=sse за март=сердечником2 -auxbase-полоски главный.СРР.o-g-O3-Wall-Wextra-std=gnu++1y-версия-fdiagnostics-цвет=авто-ftemplate-глубина=128-fno-имена операторов-o /tmp/ccxfkRyd.s

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

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

$ / gcc-4.9.4 / collect2-плагин $/gcc-4.9.4 / liblto_plugin.так что
- плагин-opt=$/gcc-4.9.4/lto-обертка-плагин-opt=-fresolution=/tmp / cchl8RtI.res-plugin-opt= - pass-through= - lgcc_s-plugin-opt= - pass-through= - lgcc-plugin-opt= - pass-through= - lpthread-plugin-opt= - pass-through=-lgcc_s - плагин-opt= - pass-through= - lgcc --eh-frame-hdr-m elf_x86_64-экспорт-динамический-динамический компоновщик /lib64/ld-linux-x86-64.Итак.2-O run / usr/lib/../lib64/crt1.о / usr / lib/../ lib64 / crti.o $/gcc-4.9.4 / crtbegin.o-L / a / lib-L/b / lib - L / C / lib -lpthread --по мере необходимости main.СРР.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 -ЛЗ -путь rpath /а/Лива:/б/Лива:/с/Либ: -lstdc++ -лм -lgcc_s -lgcc -lpthread - lc-lgcc_s-lgcc $ / gcc-4.9.4 / crtend.о / usr / lib/../lib64/crtn.o

Как видите, -lpthread упоминается два раза! Первый -lpthread (за которым следует --as-needed) is отсутствует для случая, когда он дает 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

2 ответов


учитывая точку аварии, и тот факт, что предварительная загрузка 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 " as 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 не связано.

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

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

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

EDIT2:

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

  1. использовать ld.gold вместо ld.bfd
  2. использовать --as-needed
  3. заставляя слабое определение __pthread_key_create в этом случае через инстанцирование std::shared_ptr.
  4. не связывается с pthread, или pthread после --as-needed.

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

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

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

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

потому что определение того, что необходимо как-то подозрительно. man ld говорит (выделено мной):

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

--no-as-needed

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

теперь, согласно этот отчет об ошибке, gold чтит часть "non weak undefined symbol", в то время как 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), даже используя золотой компоновщик. Поэтому я подозреваю, что он был исправлен в более поздней версии libstdc++, что также может объяснить, почему он не segfault, если вы используете системную стандартную библиотеку.


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

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

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

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