web-dev-qa-db-ja.com

タイプvector <shared_ptr <int >>の変数の宣言に関するセグメンテーション違反

コード

これがセグメンテーション違反を与えるプログラムです。

_#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_から)。ただし、_libstdc++_から_$project_dir/build/lib_を選択するのは想定であることに注意してください。

今、私はそれを次のように実行します:

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

segmentation fault
_

ただし、_LD_LIBRARY_PATH_を設定せずに実行すると。正常に動作します。


診断

1. ldd

両方の場合のldd情報を次に示します(出力を編集して、ライブラリのfullバージョン違いがある場合に言及していることに注意してください)

_$ 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 = all

また、セグメンテーション違反の場合に_LD_DEBUG=all_を有効にして、リンカー情報を確認しようとしました。 _pthread_once_記号を検索しているときに疑わしいものを見つけました。これが見つからない場合は、セグメンテーション違反が発生します(これは、次の出力スニペットの解釈です)。

_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>_を使用する代わりに、元のコードを使用し、次のように_libboost_system.so_を使用して_LD_PRELOAD_をプリロードして実行しました。

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

そしてそれはうまくいきました!

次に、_libboost_system.so_でlddを実行しました。これにより、ライブラリのリストが表示され、そのうちの2つは次のとおりです。

_  /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
_

どちらの場合も、正常に実行されました。

librtまたはlibpthread(またはboth)のいずれかをロードすることで、いくつかの要件が満たされるか、問題が回避されるという結論になりました。ただし、問題の根本的な原因はまだわかりません。


コンパイルとリンクのオプション

ビルドシステムは複雑で、デフォルトで存在するオプションがたくさんあるためです。そこで、CMakeのsetコマンドを使用して_-lpthread_を明示的に追加しようとしましたが、preloadinglibpthreadですでに確認したように、機能しました。

buildこれら2つのケースの違い(when-it-workswhen-it-gives-segfault)を確認するために、ビルドしましたverboseモードで_-v_をGCCに渡して、コンパイル段階と実際に_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 -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 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-必要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/a/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_が言及されています2回!最初の_-lpthread_(その後に_--as-needed_が続く)はmissingsegfaultが発生した場合です。これは、これら2つのケースのonlyの違いです。


どちらの場合も_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
_
34
Nawaz

クラッシュのポイントと、libpthreadのプリロードで修正されたように見えるという事実を考えると、2つのケースの実行は 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に対してリンクされている場合、特にpthread_key_createが使用可能かどうかをチェックする場合にtrueを返します。私のシステムでは、このシンボルは「/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に転送する必要があるもう1つのインラインシンボルです。

デバッグせずに何が起こっているのかを推測するのは難しいですが、__gthread_active_p()の真のブランチにヒットしているはずがない場合でも、呼び出すpthread_onceがないため、プログラムがクラッシュしていると思われます。

[〜#〜] edit [〜#〜]:そこで、いくつかの実験を行いました。std::locale::_S_initializeでクラッシュする唯一の方法は、__gthread_active_pの場合です。 trueを返しますが、pthread_onceはにリンクされていません。

libstdc ++はpthreadに対して直接リンクしませんが、pthread_xxの半分を弱いオブジェクトとしてインポートします。つまり、それらは未定義であり、リンカーエラーを引き起こしません。

明らかにpthreadをリンクするとクラッシュが消えますが、私が正しければ、主な問題は、pthreadをリンクしていなくても、libstdc++がマルチスレッド実行可能ファイル内にあると見なすということです。

現在、__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にリンクしていない、またはpthreadafter--as-neededにリンクしていない。

コメントの質問に答えるには:

なぜデフォルトで金を使用するのですか?

デフォルトでは、/usr/bin/ldを使用します。これは、ほとんどのディストリビューションで、/usr/bin/ld.bfdまたは/usr/bin/ld.goldへのシンボリックリンクです。このようなデフォルトは、update-alternativesを使用して操作できます。 RHEL5にはデフォルトでld.goldが付属していると私が理解している限り、なぜあなたの場合はld.bfdであるのかわかりません。

また、必要に応じて、ゴールドがバイナリにpthread.so依存関係を追加しないのはなぜですか?

何が必要かという定義はどういうわけか怪しげだからです。 man ldは言う(私の強調):

- 必要に応じて

-必要に応じて

このオプションは、-as-neededオプションの後にコマンドラインで指定されたダイナミックライブラリのELF DT_NEEDEDタグに影響します。通常、リンカーは次のDT_NEEDEDタグを追加します。ライブラリが実際に必要かどうかに関係なく、コマンドラインに記載されている各ダイナミックライブラリ。 --as-neededにより、DT_NEEDEDタグは、リンク内のその時点で非弱い未定義シンボルからの参照を満たすライブラリに対してのみ発行されます。通常のオブジェクトファイル、またはライブラリが他の必要なライブラリのDT_NEEDEDリストに見つからない場合は、別の必要なダイナミックライブラリからの弱くない未定義のシンボル参照。問題のライブラリの後にコマンドラインに表示されるオブジェクトファイルまたはライブラリは、ライブラリが必要に応じて表示されるかどうかには影響しません。これは、アーカイブからオブジェクトファイルを抽出するためのルールに似ています。 --no-as-neededは、デフォルトの動作を復元します。

さて、 このバグレポート によると、goldは「弱い未定義のシンボル」の部分を尊重し、ld.bfdは必要に応じて弱いシンボルを認識します。 TBH私はこれについて完全に理解していません。これがld.goldバグと見なされるのか、それともlibstdc++バグと見なされるのかについて、そのリンクについていくつかの議論があります。

-pthreadと-lpthreadの両方に言及する必要があるのはなぜですか? (-pthreadはビルドシステムによってデフォルトで渡され、ゴールドで動作するように-lpthreadを渡しました)。

-pthread-lpthreadは異なることをします( pthread vs lpthread を参照)。前者は後者を意味するべきだと私は理解しています。

とにかく、おそらく-lpthreadを1回だけ渡すことができますが、before--as-neededを渡すか、最後のライブラリの後、--no-as-neededの前に-lpthreadを使用する必要があります。

また、ゴールドリンカーを使用しても、システム(GCC 7.2)でこの問題を再現できなかったことにも言及する価値があります。したがって、より新しいバージョンのlibstdc ++で修正されたと思われます。これは、システム標準ライブラリを使用した場合にセグメンテーション違反が発生しない理由も説明している可能性があります。

13
sbabbi

これは、_libstdc++_ ABI間の微妙な不一致によって引き起こされる問題である可能性があります。 GCC4.9はRedHat Enterprise Linux 5のシステムコンパイラではないため、そこで何を使用しているかは明確ではありません(DTS3?)。

ロケールの実装は、ABIの不一致に非常に敏感であることが知られています。 gcc-helpリストで次のスレッドを参照してください。

最善の策は、_libstdc++_のどのビットがどこにリンクされているかを把握し、何らかの方法で一貫性を実現することです(シンボルを非表示にするか、互換性があるように再コンパイルします)。

Red Hatの開発者ツールセット(新しいビットは静的にリンクされていますが、C++標準ライブラリの大部分は既存のシステムDSOを使用しています)の_libstdc++_に使用されるハイブリッドリンケージモデルを調査することも役立つ場合がありますが、システムは_libstdc++_現在の言語機能のサポートが必要な場合、Red Hat Enterprise Linux5の__は古すぎる可能性があります。

9
Florian Weimer