c++ - Segfault声明类型为vector <shared_ptr <int >>的变量

标签 c++ gcc segmentation-fault redhat ld



这是给出段错误的程序。

#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;
}

当然,程序本身没有绝对没有错。段故障的根本原因取决于段的构建和运行环境。

背景

在亚马逊,我们使用一个构建系统,该系统以几乎与机器无关的方式构建和部署二进制文件(libbin)。对于我们的情况,这基本上意味着它将可执行文件(从上述程序构建)部署到$project_dir/build/bin/中,几乎所有依赖项(即共享库)都部署到$project_dir/build/lib/中。我之所以使用短语“几乎”是因为对于诸如libc.solibm.sold-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出现段错误时
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 =全部

我还尝试通过为segfault案例启用LD_DEBUG=all来查看链接器信息。我发现了一些可疑的东西,因为它搜索pthread_once符号,当找不到它时,就会出现段错误(这是我对以下输出片段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!

问题

我知道这样很难调试,而且可能我还没有提供很多有关环境及所有方面的信息。但是,我的问题仍然是:此段错误的可能根本原因是什么?如何进一步调试并找到?一旦发现问题,修复将很容易。

编译器和平台

我在RHEL5上使用 GCC 4.9

实验

E#1

如果我评论以下行:
std::vector<std::shared_ptr<int>> y {}; 

它可以编译并正常运行!

E#2

我只是在程序中包含以下 header :
#include <boost/filesystem.hpp>

并据此进行链接。现在,它可以正常运行而不会出现任何段错误。因此,似乎通过依赖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,它给出了一个lib列表,其中两个是:
  /lib64/librt.so.1
  /lib64/libpthread.so.0

因此,我没有预加载libboost_system,而是分别预加载了librtlibpthread:
$ LD_PRELOAD=/lib64/librt.so.1 $project_dir/build/bin/run

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

在这两种情况下,它都能成功运行。

现在,我的结论是,通过加载librtlibpthread(或两者),可以满足某些要求或可以解决问题!不过,我仍然不知道问题的根本原因。

编译和链接选项

由于构建系统很复杂,默认情况下有很多选项。因此,我尝试使用CMake的-lpthread命令显式添加set,然后它起作用了,因为我们已经看到,通过预加载libpthread可以起作用!

为了查看这两种情况之间的构建差异(当它工作时和当它出现段错误时),我通过将-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-through=-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 run /usr/lib/../lib64/crt1.o /usr/lib/../lib64/crti.o $/gcc-4.9.4/crtbegin.o -L/a/lib -L/b/lib -L/c/lib -lpthread --as-needed 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 /a/lib:/b/lib:/c/lib: -lstdc++ -lm -lgcc_s -lgcc -lpthread -lc -lgcc_s -lgcc $/gcc-4.9.4/crtend.o /usr/lib/../lib64/crtn.o



如您所见,两次被提及为两次!在给定segfault 的情况下,第一个-lpthread(后跟-lpthread)缺少。那是这两种情况之间的唯一区别。

两种情况下--as-needed的输出

有趣的是,两种情况下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

最佳答案

考虑到崩溃点,并且预加载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();
  }

如果您的程序与pthread链接,则__gthread_active_p()返回true,特别是它检查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

libstdc++不会直接针对pthread进行链接,但是会将pthread_xx的一半导入为弱对象,这意味着它们可以是未定义的,并且不会引起链接器错误。

显然,链接pthread将使崩溃消失,但是,如果我是对的,主要问题是,即使我们没有链接pthread,您的libstdc++仍认为它在多线程可执行文件内。

现在,__gthread_active_p使用__pthread_key_create来确定我们是否有线程。这在可执行文件中被定义为弱对象(可以为nullptr,但仍然可以)。我99%确信由于shared_ptr而存在该符号(将其删除并再次检查nm以确保正确)。
因此,某种程度上__pthread_key_create会绑定(bind)到有效地址,这可能是由于链接器标志中的最后-lpthread所致。
您可以通过在locale_init.cc:315处放置一个断点并检查您采用的分支来验证这一理论。

EDIT2 :

意见摘要,只有在满足以下所有条件的情况下,此问题才可以重现:
  • 使用ld.gold代替ld.bfd
  • 使用--as-needed
  • 强制弱化__pthread_key_create,在这种情况下,通过实例化std::shared_ptr
  • 不链接到pthread,或在 pthread之后链接--as-needed

  • 要回答评论中的问题:

    Why does it use gold by default?



    默认情况下,它使用/usr/bin/ld,在大多数发行版中,它都是/usr/bin/ld.bfd/usr/bin/ld.gold的符号链接(symbolic link)。可以使用update-alternatives操纵这种默认值。我不确定在您的情况下为什么是ld.gold,据我了解,RHEL5缺省将ld.bfd附带。

    And why does gold not add pthread.so dependency to the binary if it is needed?



    因为对所需内容的定义有些阴暗。 man ld说(重点是我):

    --as-needed

    --no-as-needed

    This option affects ELF DT_NEEDED tags for dynamic libraries mentioned on the command line after the --as-needed option. Normally the linker will add a DT_NEEDED tag for each dynamic library mentioned on the command line, regardless of whether the library is actually needed or not. --as-needed causes a DT_NEEDED tag to only be emitted for a library that at that point in the link satisfies a non-weak undefined symbol reference from a regular object file or, if the library is not found in the DT_NEEDED lists of other needed libraries, a non-weak undefined symbol reference from another needed dynamic library. Object files or libraries appearing on the command line after the library in question do not affect whether the library is seen as needed. This is similar to the rules for extraction of object files from archives. --no-as-needed restores the default behaviour.



    现在,根据this bug reportgold遵循“非弱 undefined symbol ”部分,而ld.bfd根据需要看到弱符号。 TBH我对此没有完全的了解,关于该链接是否被视为ld.gold错误或libstdc++错误,有一些讨论。

    Why do I need to mention -pthread and -lpthread both? (-pthread is passed by default by our build system, and I've pass -lpthread to make it work with gold is used).


    -pthread-lpthread做不同的事情(请参阅pthread vs lpthread)。据我了解,前者应暗示后者。

    无论如何,您可能只能传递一次-lpthread,但是您需要在--as-needed之前传递它,或者在最后一个库之后和--no-as-needed之前使用-lpthread

    还值得一提的是,即使使用黄金连接器,我也无法在我的系统(GCC 7.2)上重现此问题。
    因此,我怀疑它已在最新版本的libstdc++中得到修复,该版本也可以解释如果使用系统标准库,为什么它不会进行段错误。

    关于c++ - Segfault声明类型为vector <shared_ptr <int >>的变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47202468/

    相关文章:

    c++ - 在父进程中挂接 api 调用

    c++ - Linux g++编译错误: error: expected ',' or '...' before '||' token

    c++ - gcc 和 __attribute__((unused)) 用于自动引用

    c++ - 如何控制或优化或删除或释放 UNION 中未使用的内存

    c - 当我尝试检查空行时出现段错误?

    c - 使用指针而不是数组时出现段错误

    C++ 找到二进制数的最高有效位?

    c++ - int main() 和 int main(void) 有什么区别?

    linux - 使用 -lpthread 标志 : 时未定义对 `pthread_init' 的引用

    c - 段错误 : 11 c structures