声明向量类型变量的段错误<shared_ptr<int>>

Posted

技术标签:

【中文标题】声明向量类型变量的段错误<shared_ptr<int>>【英文标题】:Segfault on declaring a variable of type vector<shared_ptr<int>> 【发布时间】:2018-04-22 11:36:35 【问题描述】:

代码

这是给出段错误的程序。

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

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


背景

我们在 Amazon 使用构建系统,该系统以几乎机器独立的方式构建和部署二进制文件(libbin)。对于我们的案例,这基本上意味着它将可执行文件(由上述程序构建)部署到$project_dir/build/bin/ 中,几乎将其所有依赖项(即共享库)部署到 $project_dir/build/lib/ 中。为什么我使用短语 "almost" 是因为对于共享库,例如 libc.solibm.sold-linux-x86-64.so.2 和可能的其他少数几个,可执行文件从系统中挑选(即来自 /lib64) .请注意,应该$project_dir/build/lib 中选择libstdc++

现在我运行如下:

$ 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 符号,当它找不到这个时,它给出了段错误(这是我对以下输出 sn-p 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

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

#include <boost/filesystem.hpp>

并相应地链接。现在它可以在没有任何段错误的情况下工作。所以似乎通过依赖libboost_system.so.1.53.0.,满足了一些要求,或者规避了问题!

E#3

由于我在将可执行文件链接到libboost_system.so.1.53.0 时看到它可以工作,所以我一步一步地做了以下事情。

我没有在代码本身中使用#include &lt;boost/filesystem.hpp&gt;,而是使用原始代码并通过使用LD_PRELOAD 预加载libboost_system.so 来运行它,如下所示:

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

它运行成功!

接下来我在libboost_system.so 上做了ldd,它给出了一个库列表,其中两个是:

  /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(或 both ),可以满足某些要求或规避问题!不过,我仍然不知道问题的根本原因。


编译和链接选项

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

为了查看这两种情况的build区别(when-it-workswhen-it-gives-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- 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 运行 /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_c 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

【问题讨论】:

从 C++ 的角度来看,由于 ODR 违规,它看起来像 UB。编译过程中使用的标准库的符号必须与链接过程中的符号相匹配,这似乎有时不是这里的情况。只需在目标机器上重新编译即可解决此问题。如果您不能这样做,请检查哪些库版本是二进制兼容的,并确保目标机器具有并使用这样的版本。或者,您可以尝试静态链接某些库而不是使用动态链接,但您可能无法对所有内容都这样做。 只是一些随意的想法:pthread_oncelibthread 中。如果你用-pthread 选项编译你的程序,它可以解决问题吗?您说包含libboost_system.so.1.53.0 解决了您的问题,但请注意libboost_system.so.1.53.0libpthread 相关联。根据您提供的跟踪,build/private/builds/RelWithDebInfo/runpools 位于可搜索文件列表中。问:runpools需要和libphtreads关联? @Amadeus:我确实使用-pthread 编译我的代码(默认情况下,在我继承的构建设置中)。但是,由于我的代码没有使用来自libpthread 的任何symbol,因此链接器不会将其添加到可执行文件中。此外,LD_DEBUG 输出中pthread_once搜索 似乎是红鲱鱼,因为它在成功运行时不存在。这意味着,它甚至不需要。 (runpools 路径应该是 $project_dir/build/bin/run 顺便说一句;编辑了问题)。 只是一些随机的想法:pthread_once 是静态变量初始化所需要的,locale 可能需要。这是 C++11 语言支持的一部分,可以使用 -f(no)threadsafe-statics 禁用/启用它。 你有没有机会使用黄金链接器?这个bug 看起来很像…… 【参考方案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() 如果您的程序链接到 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 中崩溃的唯一方法是如果__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

cmets 的总结,只有当我们具备以下所有条件时,该问题才能重现:

    使用ld.gold 而不是ld.bfd 使用--as-needed 强制__pthread_key_create 的弱定义,在本例中通过std::shared_ptr 的实例化。 未链接到pthread,或链接pthread 之后 --as-needed

回答cmets中的问题:

为什么默认使用黄金?

默认情况下,它使用/usr/bin/ld,在大多数发行版中,它是指向/usr/bin/ld.bfd/usr/bin/ld.gold 的符号链接。可以使用update-alternatives 操作此类默认值。我不确定为什么在您的情况下它是ld.gold,据我了解,RHEL5 默认附带ld.bfd

如果需要,gold 为什么不向二进制文件添加 pthread.so 依赖项?

因为对所需内容的定义有些模糊。 man ld 说(强调我的):

--根据需要

--不需要的

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

现在,根据this bug report,gold 尊重“非弱未定义符号”部分,而ld.bfd 根据需要看到弱符号。 TBH 我对此没有完全理解,关于该链接是否应被视为ld.gold 错误或libstdc++ 错误的一些讨论。

为什么我需要同时提到 -pthread 和 -lpthread ? (-pthread 是 我们的构建系统默认通过,我已经通过 -lpthread 来制作 它与黄金一起使用)。

-pthread-lpthread 做不同的事情(参见 pthread vs lpthread)。我的理解是前者应该暗示后者。

无论如何,您可能只能通过一次-lpthread,但您需要在--as-needed 之前进行,或者在最后一个库之后和-lpthread 之前使用--no-as-needed。 p>

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

【讨论】:

__gthread_oncepthread_once 一样吗?它们似乎是不同的 API。前者似乎在libgcc 中定义,而后者在libpthread 中定义。 "__gthread_once 是另一个应始终转发到 pthread_once 的内联符号。"... 是否记录在案? @Nawaz ghtr-posix.h:696。但也可能是gthr-single.h,具体取决于某些配置。 已将编译和链接选项添加到问题中。请看! @Nawaz 是的,它做了类似的事情。但是有一个名为GTHREAD_USE_WEAK 的libstdc++ 构建标志,如果将其定义为0,则检查显然被删除。我希望在这种情况下,生成的libstdc++ 应该明确链接到pthread。我将尝试在没有该标志的情况下构建 libstdc++,看看会发生什么。【参考方案2】:

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

众所周知,语言环境实现对 ABI 不匹配非常敏感。在 gcc-help 列表中查看此线程:

Binary compatibility between an old static libstdc++ and a new dynamic one plus follow-ups in the next month

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

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

【讨论】:

“GCC 4.9 不是 Red Hat Enterprise Linux 5 上的系统编译器”是什么意思?你的意思是GCC 4.9 不是为RHEL5 构建的,因此它可能无法正常工作?您能否再详细说明一下,因为这似乎很重要? Red Hat Enterprise Linux 最初附带 GCC 4.1,GCC 4.4 作为一个选项(以gcc44 包的形式)。这不是那么重要,但它表明您可能会遇到 ABI 问题,因为您使用了多个编译器,而这些编译器的构建方式并未构建兼容的二进制文件。 已将编译和链接选项添加到问题中。请看!

以上是关于声明向量类型变量的段错误<shared_ptr<int>>的主要内容,如果未能解决你的问题,请参考以下文章

c++ 类向量中的段错误

推送到成员向量时的段错误

使用 vector<vector<int> > 成员实例化对象时的段错误

由于变量范围导致的段错误

未指定 lambda 函数的返回类型时的段错误

错误帮助:ISO C++ 禁止声明没有类型的“向量”