声明向量类型变量的段错误<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 使用构建系统,该系统以几乎机器独立的方式构建和部署二进制文件(lib
和 bin
)。对于我们的案例,这基本上意味着它将可执行文件(由上述程序构建)部署到$project_dir/build/bin/
中,几乎将其所有依赖项(即共享库)部署到 $project_dir/build/lib/
中。为什么我使用短语 "almost" 是因为对于共享库,例如 libc.so
、libm.so
、ld-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 <boost/filesystem.hpp>
,而是使用原始代码并通过使用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
,而是分别预加载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
(或 both ),可以满足某些要求或规避问题!不过,我仍然不知道问题的根本原因。
编译和链接选项
由于构建系统很复杂,并且默认情况下有很多选项。所以我尝试使用 CMake 的 set
命令显式添加 -lpthread
,然后它起作用了,正如我们已经看到的那样,通过 预加载 libpthread
它起作用了!
为了查看这两种情况的build区别(when-it-works和when-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_once
在libthread
中。如果你用-pthread
选项编译你的程序,它可以解决问题吗?您说包含libboost_system.so.1.53.0
解决了您的问题,但请注意libboost_system.so.1.53.0
与libpthread
相关联。根据您提供的跟踪,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_once
和 pthread_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>>的主要内容,如果未能解决你的问题,请参考以下文章