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

Naw*_*waz 33 c++ gcc redhat ld segmentation-fault

这是给出段错误的程序.

#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;
}
Run Code Online (Sandbox Code Playgroud)

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


背景

我们在亚马逊上使用构建系统,该系统以几乎与机器无关的方式构建和部署二进制文件(libbin).对于我们的情况下,基本上意味着它部署的可执行文件(从上面的程序内置)到和几乎所有的依赖(即共享库)进入.为什么我使用的短语"几乎"是因为共享库,比如,,和可能的其他几个人,从系统(即从可执行选秀权).请注意,这是应该挑选从不过.$project_dir/build/bin/$project_dir/build/lib/libc.solibm.sold-linux-x86-64.so.2/lib64libstdc++$project_dir/build/lib

现在我运行如下:

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

segmentation fault
Run Code Online (Sandbox Code Playgroud)

但是,如果我运行它,而不设置LD_LIBRARY_PATH.它运行正常.


诊断

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)
Run Code Online (Sandbox Code Playgroud)

没有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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

3. LD_DEBUG =全部

我还尝试通过启用LD_DEBUG=allsegfault案例来查看链接器信息.我发现了一些可疑的东西,因为它搜索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]
Run Code Online (Sandbox Code Playgroud)

但是pthread_once当它成功运行时我没有看到任何情况!


问题

我知道很难像这样调试,可能我没有提供很多有关环境和所有信息的信息.但是,我的问题是:这个段错误可能是根本原因是什么?如何进一步调试并找到?一旦我发现问题,修复就很容易了.


编译器和平台

我在RHEL5上使用GCC 4.9.


实验

E·1

如果我评论以下行:

std::vector<std::shared_ptr<int>> y {}; 
Run Code Online (Sandbox Code Playgroud)

它编译并运行良好!

E·2

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

#include <boost/filesystem.hpp>
Run Code Online (Sandbox Code Playgroud)

并据此相关联.现在它没有任何段错误.因此,似乎依赖于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
Run Code Online (Sandbox Code Playgroud)

它成功运行了!

接下来我做lddlibboost_system.so这给了库列表,其中两个是:

  /lib64/librt.so.1
  /lib64/libpthread.so.0
Run Code Online (Sandbox Code Playgroud)

因此,而不是预加载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
Run Code Online (Sandbox Code Playgroud)

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

现在我的结论是,通过加载任何一个librtlibpthread(或两个),满足一些要求或避免问题!不过,我仍然不知道问题的根本原因.


编译和链接选项

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

为了查看这两种情况之间的构建差异(当它工作时何时给出段错误),我通过传递给GCC 以详细模式构建它-v,以查看编译阶段及其实际传递给的选项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.od - 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条main.cpp.o -g -O3 -Wall -Wextra -std = GNU ++ 1Y -version -fdiagnostics色=自动-ftemplate深入= 128 -fno-运营商名称-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-包装-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通= -lgcc_s -plugin-OPT = -pass通= -lgcc --eh帧-HDR -m elf_x86_64 -export动态-dynamic接头/ 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/C/LIB -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 /一个/ 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

如你所见,-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
Run Code Online (Sandbox Code Playgroud)

sba*_*bbi 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();
  }
Run Code Online (Sandbox Code Playgroud)

__gthread_active_p()如果您的程序与pthread链接,则返回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是if __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并检查您所采用的分支来验证此理论.

编辑2:

评论摘要,如果我们拥有以下所有内容,则该问题仅可重现:

  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.so依赖?

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

- 如所须

--no-按需

此选项会影响--as-needed选项后命令行中提到的动态库的ELF DT_NEEDED标记. 通常,链接器将为命令行中提到的每个动态库添加DT_NEEDED标记,无论是否实际需要库.--as急需使DT_NEEDED标记仅被发射为在链接点满足库非弱未定义的符号从一个普通对象文件引用;或者,如果库中没有的其它的DT_NEEDED列表中发现需要的库,来自另一个所需动态库的非弱未定义符号引用.在相关库之后出现在命令行上的对象文件或库不会影响是否根据需要查看库.这类似于从归档中提取目标文件的规则.--no-as-need恢复默认行为.

现在,根据这个错误报告,gold尊重"非弱未定义符号"部分,同时ld.bfd根据需要看到弱符号.TBH我对此没有充分的理解,并且关于这是否被视为ld.gold错误或libstdc++错误的链接存在一些讨论.

为什么我需要提到-pthread和-lpthread?(-pthread默认由我们的构建系统传递,我传递-lpthread以使其与黄金一起使用).

-pthread-lpthread做不同的事情(见并行线程VS lpthread).我的理解是,前者应该暗示后者.

无论如何,你可能-lpthread只能通过一次,但你需要在之前完成 --as-needed,或者--no-as-needed在最后一个库之后使用它-lpthread.

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


Flo*_*mer 9

这可能是由libstdc++ABI 之间的微妙不匹配引起的问题.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中的系统如果您需要支持当前的语言功能,可能会因此而过时.