11

Linux プラットフォーム用に 2 つの外部委託共有ライブラリがあります (ソースもドキュメントもありません)。ライブラリは、個別にプログラムにリンクされている場合 (g++ xx.cpp lib1.so、または g++ xx.cpp lib2.so)、正常に動作します。

ただし、C++ プログラムがこれら 2 つの共有ライブラリに同時にリンクされると、プログラムは「二重解放」エラー (g++ xx.cpp lib1.so lib2.so) で必然的にクラッシュします。

C++ プログラムが空のhello world プログラムであり、これらのライブラリとは関係がない場合でも、クラッシュします。

#include <iostream>
using namespace std;
int main(){
     cout<<"haha, I crash again. Catch me if you can"<<endl;
     return 0;
}

メイクファイル:

g++ helloword.cpp lib1.so lib2.so

これらの lib1.so lib2.so ライブラリがいくつかの共通グローバル変数を共有し、いくつかの変数を 2 回破棄する可能性があるという手がかりを得ました。gdb と valgrind を試しましたが、バックトレースから有用な情報を抽出できません。

これら 2 つの共有ライブラリを分離して、サンドボックスのように機能させる方法はありますか?

EDITED(コアダンプとgdbバックトレースを追加):

前述のおもちゃの空の helloword プログラムを 2 つのライブラリにリンクしました (プラットフォーム: centos 7.0 64bits with gcc4.8.2):

g++ helloworld.cpp  lib1.so lib2.so -o check

ヴァルグラインド:

==29953== Invalid free() / delete / delete[] / realloc()
==29953==    at 0x4C29991: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==29953==    by 0x613E589: __cxa_finalize (in /usr/lib64/libc-2.17.so)
==29953==    by 0x549B725: ??? (in /home/fanbin/InventoryManagment/lib1.so)
==29953==    by 0x5551720: ??? (in /home/fanbin/InventoryManagment/lib1.so)
==29953==    by 0x613E218: __run_exit_handlers (in /usr/lib64/libc-2.17.so)
==29953==    by 0x613E264: exit (in /usr/lib64/libc-2.17.so)
==29953==    by 0x6126AFB: (below main) (in /usr/lib64/libc-2.17.so)
==29953==  Address 0x6afb780 is 0 bytes inside a block of size 624 free'd
==29953==    at 0x4C29991: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==29953==    by 0x613E589: __cxa_finalize (in /usr/lib64/libc-2.17.so)
==29953==    by 0x4F07AC5: ??? (in /home/fanbin/InventoryManagment/lib2.so)
==29953==    by 0x5039900: ??? (in /home/fanbin/InventoryManagment/lib2.so)
==29953==    by 0x613E218: __run_exit_handlers (in /usr/lib64/libc-2.17.so)
==29953==    by 0x613E264: exit (in /usr/lib64/libc-2.17.so)
==29953==    by 0x6126AFB: (below main) (in /usr/lib64/libc-2.17.so)

gdb バックトレース メッセージ:

(gdb) bt
#0  0x00007ffff677d989 in raise () from /lib64/libc.so.6
#1  0x00007ffff677f098 in abort () from /lib64/libc.so.6
#2  0x00007ffff67be197 in __libc_message () from /lib64/libc.so.6
#3  0x00007ffff67c556d in _int_free () from /lib64/libc.so.6
#4  0x00007ffff7414aa2 in __tcf_0 () from ./lib1.so
#5  0x00007ffff678158a in __cxa_finalize () from /lib64/libc.so.6
#6  0x00007ffff739f726 in __do_global_dtors_aux () from ./lib1.so
#7  0x0000000000600dc8 in __init_array_start ()
#8  0x00007fffffffe2c0 in ?? ()
#9  0x00007ffff7455721 in _fini () from ./lib1.so
#10 0x00007fffffffe2c0 in ?? ()
#11 0x00007ffff7debb98 in _dl_fini () from /lib64/ld-linux-x86-64.so.2
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

アップデート

@RaduChivu の助けに感謝します。非常によく似たシナリオを見つけました。 プログラムの終了時に __tcf_0 でセグメンテーション違反が発生し、実際に 2 つのライブラリ間でグローバル変数の衝突が発生しているように見えます。これら 2 つの外部共有ライブラリのソース ファイルがないことを考えると、2 つの別個のプロセスを使用する場合を除いて、この競合を解決できる他の方法はありますか?

4

2 に答える 2

3

私は 1 日の検索の後にこの問題を解決しました。今後誰かがこの問題に遭遇した場合に備えて、ここにメモを残しておきます。

説明

@RaduChivn と私の推測が正しいことを証明しています。2 つの共有ライブラリが共通のグローバル変数を共有している可能性があります。空のプログラムが 2 つの共有ライブラリの両方に同時にリンクされている場合でも、プログラムが終了するときに、共通のグローバル変数が 2 回解放されようとするため、2 回の解放による破損が発生します。

手がかりは、gdb バックトレースの次のメッセージから得られます。

#4  0x00007ffff7414aa2 in __tcf_0 () from ./lib1.so

このスレッドで説明されているように:

関数 __tcf_0 とは何ですか? (gprof と g++ の使用時に見られる)

tcf_0 は、g++ によって生成され、exit() がトリガーされたときに静的オブジェクトを破棄する関数です。このメッセージは、ある共有ライブラリが別の共有ライブラリの後に終了しようとすると、二重解放が発生することを示唆しています。

これら 2 つのライブラリは連携して動作するように設計されているため、破損は容認できないエンジニアの災害です。このような低品質でありながら明らかなバグが、5 つのバージョン リリースの間、どのように生き残ることができるでしょうか? これはおそらく、大部分のライブラリ ユーザーが Windows プラットフォームで作業しているためです (そのパッケージは正常に動作します)。しかし、この仮定は間違いの原因に関する別のヒントを提供します。共有ライブラリは Windows ではうまく機能しますが、Linux ではクラッシュします。バグの原因は、OS に依存する動作の違いに違いありません。このスレッドはいくつかの洞察を提供します:

exec と共有 libaray の両方でコンパイルされた場合、グローバル変数には、Windows では複数のコピーがあり、Linux では 1 つのコピーがあります

つまり、共有ライブラリの「外部グローバル」は、Linux では単一のコピーを取得しますが、Windows では複数のコピーを取得します。

解決

(1) 当然ながら、2 つのプロセスを作成し、それぞれが 1 つのライブラリに個別にリンクするという回避策があります。

(2) @DavidSchwartz は、一般的な "return 0" または "exit(0)" の代わりに、プログラムの最後で _exit(0) を使用する別の回避策を提供しています。によると

従来の Linux fork-exec での _exit() と exit() の使用の違いは何ですか?

、手動でファイルをフラッシュし、atexit ジョブを確認する必要があります。メモリに関しては、プログラムが終了しているため、OSはとにかくすべてのプロセスメモリを再利用しますが、心配する必要はありません。

(3) もう 1 つの方法は、dlopen(xx.so, RTLD_LOCAL) を使用して、最初にすべてのシンボルをブラインドしてから、必要な関数シンボルを手動で dlysm することです。

(@JonathanWakely は、ここで RTLD_LOCAL に副作用があることに注意してください。コメントを参照してください)。

まさにこの場合、ライブラリ コーダーは共有ライブラリで「extern C」を使用していませんでした。他の誰かがこれを楽しんでいる場合は、次のスレッドが役立つ場合があります。

共有ライブラリの動的読み込み中に未定義のシンボル エラーが発生する

私の場合のように、共有ライブラリが十分にサポートされていなくても、解決策はまだあります。必要なすべての関数を手動で整理し、 nm を使用して .so ファイル内の対応する各シンボルを見つけ、それらを 1 つずつリンクすると、機能しました。

于 2014-08-01T08:21:28.773 に答える