Segfault声明类型为vector的变量<shared_ptr <int>&gt;

时间:2017-11-09 12:58:38

标签: 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;
}

当然,程序本身中绝对没有错误。 segfault的根本原因取决于其构建和运行的环境。

背景

我们在亚马逊上使用构建系统,该系统以几乎机器独立的方式构建和部署二进制文件(libbin)。对于我们的情况,这基本上意味着它将可执行文件(从上面的程序构建)部署到$project_dir/build/bin/几乎所有依赖项(即共享库)到$project_dir/build/lib/。为什么我使用短语&#34;几乎&#34; 是因为对于共享库,例如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何时发生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启用段错误案例来查看链接器信息。我发现了一些可疑的东西,因为它搜索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

实验

电子#1

如果我评论以下一行:

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

编译并运行良好!

电子#2

我刚刚将以下标题包含在我的程序中:

#include <boost/filesystem.hpp>

并相应地联系起来。现在它没有任何段错误。所以似乎依赖于libboost_system.so.1.53.0.,满足了一些要求,或者规避了问题!

电子#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它可以工作!

为了查看这两种情况之间 build 的区别( when-it-works when-it-give-segfault ) ,我通过将-v传递给GCC来在 verbose 模式下构建它,以查看编译阶段及其实际传递给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 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 /一个/ 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。 Ø

如您所见,-lpthread被提及两次!第一个-lpthread(后跟--as-needed缺少 ,因为它提供了段错误。这是这两种情况之间唯一的区别。

两种情况下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 个答案:

答案 0 :(得分:12)

考虑到崩溃点,以及预加载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会使崩溃消失,但如果我是对的,主要的问题是你的libstdc++认为它在多线程可执行文件中,即使我们没有链接pthread。

现在,__gthread_active_p使用__pthread_key_create来判断我们是否有线程。这在您的可执行文件中定义为弱对象(可以是nullptr并且仍然可以)。由于shared_ptr,我99%确定符号在那里(删除它并再次检查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
  5. 回答评论中的问题:

      

    为什么默认使用黄金?

    默认情况下,它使用/usr/bin/ld,在大多数发行版上都是/usr/bin/ld.bfd/usr/bin/ld.gold的符号链接。可以使用update-alternatives来操纵此类默认值。我不知道为什么在你的情况下它是ld.gold,据我所知RHEL5默认配有ld.bfd

      

    如果需要,黄金为什么不在二进制文件中添加pthread.so依赖?

    因为所需要的定义在某种程度上是阴暗的。 man ld说(强调我的):

      

    - 按需

         

    - 无按需

         

    此选项会影响--as-needed选项后命令行上提到的动态库的ELF DT_NEEDED标记。   通常,链接器将添加DT_NEEDED              无论是否实际需要库,命令行中提到的每个动态库的标记。   --as-needed会导致DT_NEEDED标记              仅针对链接中的该点满足常规非弱未定义符号引用的库发出   目标文件,或者,如果库不是              在其他所需库的DT_NEEDED列表中找到,来自另一个所需动态的非弱未定义符号引用   图书馆。对象文件或库              在有问题的库之后出现在命令行上不会影响是否按需查看库。这是类似的   提取规则              来自档案的目标文件。 --no-as-need恢复默认行为。

    现在,根据this bug reportgold尊重“非弱未定义符号”部分,而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 ++中得到修复,这也可以解释为什么如果你使用系统标准库它不会出现段错误。

答案 1 :(得分:9)

这可能是由libstdc++ ABI之间的微妙不匹配引起的问题。 GCC 4.9不是Red Hat Enterprise Linux 5上的系统编译器,因此目前还不清楚你在那里使用什么(DTS 3?)。

已知区域设置实现对ABI不匹配非常敏感。请参阅gcc-help列表中的此主题:

你最好的办法是找出哪些位置libstdc++链接在哪里,并以某种方式实现一致性(通过隐藏符号或重新编译事物以使它们兼容)。

在Red Hat的Developer Toolset中调查用于libstdc++的混合链接模型也可能有用(其中较新的位是静态链接的,但是大部分C ++标准库使用现有的系统DSO),但是如果您需要对当前语言功能的支持,Red Hat Enterprise Linux 5中的系统libstdc++可能太旧了。