web-dev-qa-db-ja.com

g ++静的リンクpthreadの場合、セグメンテーション違反が発生するのはなぜですか?

#include <iostream>
#include <map>
#include <thread>

#define SIZE 1024
#define AMOUNT 100000
#define THREADS 4

class A
{
private:
    char a[SIZE];
};

void test()
{
    std::cout << "test start\n";
    std::map<int, A*> container;
    for(int i=0; i<AMOUNT; i++)
    {
        A* a = new A();
        std::pair<int, A*>p = std::make_pair(i, a);
        container.insert(p);
    }

    std::cout << "test release\n";
    for(int i=0; i<AMOUNT; i++)
    {
        auto iter = container.find(i);
        delete iter->second;
        container.erase(iter);
    }
    std::cout << "test end\n";
}

int main()
{
    std::thread ts[THREADS];
    for(int i=0; i<THREADS; i++)
    {
        ts[i] = std::thread(test);
    }

    for(std::thread& x: ts)
    {
        x.join();
    }

    return 0;
}

上記は単純なc ++コードです。

コンパイル:g++ -pthread -o one one.cpp -Wall -std=c++11 -O3

ldd one、得点:

    linux-vdso.so.1 =>  (0x00007ffebafce000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb47352a000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb473313000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb4730f4000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb472d2a000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb472a22000)
    /lib64/ld-linux-x86-64.so.2 (0x00005654c5112000)

./oneを実行すると、すべてが大丈夫です。

次に、静的リンクを試します:g++ -pthread -o one one.cpp -Wall -std=c++11 -O3 -static

ldd one、得点:

    not a dynamic executable

しかし、実行すると、問題が発生します...

test start
Segmentation fault (core dumped)

-gで再コンパイルすると、gdbに次のように表示されます。

wang[00:35][~/test]$ gdb one
GNU gdb (Ubuntu 7.10-1ubuntu2) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos Word" to search for commands related to "Word"...
Reading symbols from one...done.
(gdb) run
Starting program: /home/wang/test/one 
[Thread debugging using libthread_db enabled]
Using Host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7ffa700 (LWP 3623)]
test start
[New Thread 0x7ffff77f8700 (LWP 3624)]
test start
[New Thread 0x7ffff6ff7700 (LWP 3625)]
test start
[New Thread 0x7ffff67f6700 (LWP 3626)]
test start

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) 

なんでこれ?

[〜#〜]更新[〜#〜]============================ ==

boost::threadライブラリを使用(ブーストバージョン:1.60)、

std::threadboost::threadに置き換え、静的リンクを作成します。

g++ -pthread -o one1 one.cpp -Wall -std=c++11 -O3 -I /opt/boost/include/ -L /opt/boost/lib/ -lboost_system -lboost_thread -static

問題は発生しませんでした!

混乱しています...

27
Yueyoum

まず、解決策。これはここで動作します:

更新:buntu 18.04以降 、librtにもリンクする必要があります(-lrtを追加):

g++ -o one one.cpp -Wall -std=c++11 -O3 -static -lrt -pthread \
    -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

(元の答えで続行)

g++ -o one one.cpp -Wall -std=c++11 -O3 -static -pthread \
    -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

-pthreadを使用すると、コンパイラーはすでにpthreadにリンクします(そしてプラットフォームによっては、-D_REENTRANTのような追加のマクロが定義されます。詳細は この質問 を参照してください)。

では、-pthread-lpthreadを意味する場合、静的にリンクするときに-lpthreadを指定する必要があるのはなぜですか。そして、Wl,--whole-archiveは何をしますか?

弱いシンボルを理解する

Unixでは、 [〜#〜] elf [〜#〜] ファイル形式が使用され、これには 弱いシンボルと強いシンボル の概念があります。 Wikipedia page から引用するには:

デフォルトでは、注釈がない場合、オブジェクトファイル内のシンボルはstrongです。リンク中、強いシンボルは同じ名前のweakシンボルを上書きできます。対照的に、名前を共有する2つの強いシンボルは、リンク時にリンクエラーになります。

動的ライブラリと静的ライブラリに関しては、微妙な違いがあります。静的ライブラリでは、リンカは弱いシンボルであっても最初のシンボルで停止し、強いシンボルの検索を停止します。 (動的にリンクされたライブラリの場合と同じように)すべてのシンボルを参照するように強制するには、ld--whole-archiveオプションをサポートします。

man ld から引用するには:

--whole-archive: --whole-archiveオプションの後にコマンドラインで指定された各アーカイブについて、必要なオブジェクトファイルをアーカイブで検索するのではなく、アーカイブ内のすべてのオブジェクトファイルをリンクに含めます。これは通常、アーカイブファイルを共有ライブラリに変換するために使用され、すべてのオブジェクトが結果の共有ライブラリに含まれるようにします。このオプションは複数回使用できます。

さらに、gccからオプションを-Wl,--whole-archiveとして渡す必要があることを説明します。

Gccからこのオプションを使用する場合の2つの注意:最初に、gccはこのオプションを認識しないため、-Wl、-whole-archiveを使用する必要があります。次に、アーカイブのリストの後に-Wl、-no-whole-archiveを使用することを忘れないでください。これは、gccが独自のアーカイブのリストをリンクに追加し、このフラグがそれらにも影響を与えたくない場合があるためです。

そして、それを再びオフにする方法を説明します:

--no-whole-archive:後続のアーカイブファイルの--whole-archiveオプションの効果をオフにします。

Pthreadおよびlibstdc ++の弱いシンボル

弱いシンボルの使用例の1つは、実装を最適化されたものと交換できるようにすることです。もう1つはスタブを使用する方法で、後で必要に応じて置き換えることができます。

たとえば、fputc概念的にはprintf によって使用される)は、POSIXでスレッドセーフである必要があり、同期する必要があるため、コストがかかります。シングルスレッド環境では、コストを支払う必要はありません。したがって、実装では、同期関数を空のスタブとして実装し、関数をウィークシンボルとして宣言できます。

後で、マルチスレッドライブラリがリンクされている場合(pthreadなど)、シングルスレッドサポートが意図されていないことが明らかになります。マルチスレッドライブラリをリンクすると、リンカはスタブを実際の同期関数(強力なシンボルとして定義され、スレッドライブラリによって実装される)に置き換えることができます。一方、マルチスレッドライブラリがリンクされていない場合、実行可能ファイルは同期関数にスタブを使用します。

glibc(fputcを提供)およびpthreadは、まさにこのトリックを使用しているようです。詳細はこちら glibcでの弱いシンボルの使用に関する質問 を参照してください。上記の例は this answer からの引用です。

nm を使用すると、詳細を確認できます。これは、上記の回答と一致しているようです。

$ nm /usr/lib/libc.a 2>/dev/null | grep pthread_mutex_lock
w __pthread_mutex_lock
... (repeats)

「w」は「弱い」を意味するため、静的にリンクされたlibcライブラリには、弱いシンボルとして__pthread_mutex_lockが含まれています。静的にリンクされたpthreadライブラリには、強力なシンボルとして含まれています。

$ nm /usr/lib/libpthread.a 2>/dev/null | grep pthread_mutex_lock
             U pthread_mutex_lock
pthread_mutex_lock.o:
00000000000006a0 T __pthread_mutex_lock
00000000000006a0 T pthread_mutex_lock
0000000000000000 t __pthread_mutex_lock_full

サンプルプログラムに戻る

動的にリンクされた実行可能ファイルの共有ライブラリの依存関係を調べると、マシンでlddとほぼ同じ出力が得られます。

$ ldd one
linux-vdso.so.1 (0x00007fff79d6d000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007fcaaeeb3000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007fcaaeb9b000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fcaae983000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fcaae763000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fcaae3bb000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcaaf23b000)

ltrace を使用してライブラリ呼び出しを出力すると、次の出力が得られます。

$ ltrace -C ./one 
std::ios_base::Init::Init()(0x563ab8df71b1, 0x7ffdc483cae8, 0x7ffdc483caf8, 160) = 0
__cxa_atexit(0x7fab3023bc20, 0x563ab8df71b1, 0x563ab8df7090, 6)         = 0
operator new(unsigned long)(16, 0x7ffdc483cae8, 0x7ffdc483caf8, 192)    = 0x563ab918bc20
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80) = 0
operator new(unsigned long)(16, 0x7fab2f6a1fb0, 0, 0x800000)            = 0x563ab918bd70
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80) = 0
operator new(unsigned long)(16, 0x7fab2eea0fb0, 0, 0x800000)            = 0x563ab918bec0
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80test start
) = 0
operator new(unsigned long)(16, 0x7fab2e69ffb0, 0, 0x800000)            = 0x563ab918c010
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80test start
test start
) = 0
std::thread::join()(0x7ffdc483c9a0, 0x7fab2de9efb0, 0, 0x800000test start
test release
test release
test release
test release
test end
test end
test end
test end
)        = 0
std::thread::join()(0x7ffdc483c9a8, 0x7fab2eea19c0, 0x7fab2f6a2700, 0)  = 0
std::thread::join()(0x7ffdc483c9b0, 0x7fab2e6a09c0, 0x7fab2eea1700, 0)  = 0
std::thread::join()(0x7ffdc483c9b8, 0x7fab2de9f9c0, 0x7fab2e6a0700, 0)  = 0
+++ exited (status 0) +++

例として、std::thread::joinが呼び出されます。これは、おそらく内部でpthread_joinを使用します。そのシンボルは、ldd出力にリストされている(動的にリンクされた)ライブラリ、つまりlibstdc++.so.6およびlibpthread.so.0にあります。

$ nm /usr/lib/libstdc++.so.6 | grep pthread_join
                 w pthread_join

$ nm /usr/lib/libpthread.so.0 | grep pthread_join
0000000000008280 T pthread_join

動的にリンクされた実行可能ファイルでは、リンカーは弱いシンボルを強いシンボルに置き換えます。この例では、静的にリンクされたライブラリに同じセマンティクスを適用する必要があります。そのため、-Wl,--whole-archive -lpthread -Wl,--no-whole-archiveが必要です。

それを見つけることは少し試行錯誤です。少なくとも、その件に関する明確な文書は見つかりませんでした。 Linuxでの静的リンクはむしろEdgeケースになっている であると思いますが、動的リンクは多くの場合、ライブラリの使用方法の標準的なアプローチです(比較については 静的リンクと動的リンク )。私が見て、個人的にそれを機能させるためにしばらく苦労した最も極端な例は、 TBBを静的にリンクする です。

付録:Autotoolsの回避策

AutomakeではLDADDのオプションを設定できないため、ビルドシステムとしてautotoolsを使用している場合は、回避策が必要です。残念ながら、あなたは書くことができません:

(Makefile.am)
mytarget_LDADD = -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

回避策として、configure.acでフラグを定義し、次のように使用することで、チェックを回避できます。

(configure.ac)
WL_WHOLE_ARCHIVE_HACK="-Wl,--whole-archive"
WL_NO_WHOLE_ARCHIVE_HACK="-Wl,--no-whole-archive"
AC_SUBST(WL_WHOLE_ARCHIVE_HACK)
AC_SUBST(WL_NO_WHOLE_ARCHIVE_HACK)

(Makefile.am)
mytarget_LDADD = @WL_WHOLE_ARCHIVE_HACK@ -lpthread @WL_NO_WHOLE_ARCHIVE_HACK@
49
Philipp Claßen

Pthreadを使用するビルド済みのC++ .aアーカイブをリンクするときにも、同様の問題が発生しました。私の場合、-Wl,--whole-archive -lpthread -Wl,--no-whole-archiveに加えて、弱いシンボルごとに-Wl,-u,...も必要です。

私の症状は実行時のクラッシュであり、gdbを使用して逆アセンブルすると、クラッシュはcallq 0x0の直後であることがわかりました。一部の検索を行い、他の人が静的pthreadリンクでこれを見たことがあることを発見しました。

nmを使用して強制的に解決する記号を見つけ、w記号を探します。リンクした後、callq 0x0命令がさまざまなシンボルアドレスでpthread_mutex_lockなどに更新されていることがわかりました。

0
Mattias Wadman