这是给出段错误的程序。
#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的根本原因取决于其构建和运行的环境。
我们在亚马逊上使用构建系统,该系统以几乎机器独立的方式构建和部署二进制文件(lib
和bin
)。对于我们的情况,这基本上意味着它将可执行文件(从上面的程序构建)部署到$project_dir/build/bin/
和几乎所有依赖项(即共享库)到$project_dir/build/lib/
。为什么我使用短语&#34;几乎&#34; 是因为对于共享库,例如libc.so
,libm.so
,ld-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
。它运行正常。
以下是这两种情况的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)
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)
我还尝试通过为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 。
如果我评论以下一行:
std::vector<std::shared_ptr<int>> y {};
编译并运行良好!
我刚刚将以下标题包含在我的程序中:
#include <boost/filesystem.hpp>
并相应地联系起来。现在它没有任何段错误。所以似乎依赖于libboost_system.so.1.53.0.
,满足了一些要求,或者规避了问题!
因为当我将可执行文件链接到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
,而是分别预加载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
(或两者),可以满足某些要求或避免问题!不过,我仍然不知道问题的根本原因。
由于构建系统很复杂,默认情况下有很多选项。所以我尝试使用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
答案 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 强>:
评论摘要,如果我们拥有以下所有内容,则该问题仅可重现:
ld.gold
代替ld.bfd
--as-needed
__pthread_key_create
的弱定义,在这种情况下,通过std::shared_ptr
的实例化。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
说(强调我的):
- 按需
- 无按需
此选项会影响--as-needed选项后命令行上提到的动态库的ELF DT_NEEDED标记。 通常,链接器将添加DT_NEEDED 无论是否实际需要库,命令行中提到的每个动态库的标记。 --as-needed会导致DT_NEEDED标记 仅针对链接中的该点满足常规非弱未定义符号引用的库发出 目标文件,或者,如果库不是 在其他所需库的DT_NEEDED列表中找到,来自另一个所需动态的非弱未定义符号引用 图书馆。对象文件或库 在有问题的库之后出现在命令行上不会影响是否按需查看库。这是类似的 提取规则 来自档案的目标文件。 --no-as-need恢复默认行为。
现在,根据this bug report,gold
尊重“非弱未定义符号”部分,而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++
可能太旧了。