18

リンカーに厄介な問題があります。いくつかのシンボルを共有ライブラリから静的ライブラリにリンクしたいが、そのシンボルをエクスポートしたくない (つまり、単純にライブラリをマージしたり とリンクしたりすることはできない--whole-archive)。私が欲しいのは、共有ライブラリを静的なものにリンクし(実行可能ファイルをリンクする、未定義のシンボルを解決するなど)、未定義のシンボルを削除することです。

私が探しているのはおそらく単なるリンカー オプションですが、指を置くことはできません。

問題をできる限り説明し (それほど簡単ではありません)、おもちゃで最小限の例を示して遊んでみます。

編集:問題は解決されました。解決策は質問の最後に投稿されました

簡単な説明:

LD_PRELOADこのトリックを使用して、一部の関数呼び出しを実行可能ファイルにトラップしたいと考えています。この実行可能ファイルは、トラップしたい関数の関数定義を含むサード パーティの共有ライブラリに対してリンクされています。

このサードパーティのライブラリには、別のライブラリのシンボルも含まれています。これは、自分のライブラリでも使用していますが、別の (互換性のない) バージョンです。

私がやりたいことは、共有ライブラリをコンパイルし、コンパイル時にシンボルをエクスポートせずに最後の (静的) ライブラリの定義とリンクして、共有ライブラリがトラップしたいバージョンとは異なるバージョンを使用するようにすることです。

簡素化された問題の説明

というサードパーティ ライブラリlibext.soがありますが、そのソース コードはありません。これは関数を定義し、別のライブラリbarの関数を使用しますが、シンボルは両方ともそこで定義されています。foo

$> nm libext.so
0000000000000a16 T bar
00000000000009e8 T foo

前述fooしたように、新しいバージョンを使用したい外部依存関係です。私はそれのために更新されたライブラリを持っています、それを呼びましょうlibfoo.a

$> nm libfoo.a
0000000000000000 T foo

問題は、 を再定義する動的ライブラリを作成したいのですが、ライブラリでfrombarの定義を使用し、 from の関数でfromの関数を呼び出したいことです。言い換えれば、ライブラリのコンパイル時のリンケージが必要です。foolibfoo.alibext.sofoolibext.solibfoo.a

私が探しているのはlibfoo.a、シンボルを使用するがエクスポートしないライブラリを定義することです。ライブラリを にリンクするとlibfoo.a、次のようになります。

$> nm libmine.so
0000000000000a78 T bar
0000000000000b2c T foo

つまり、 と の両方をオーバーロードfoobarます ( をオーバーライドしたくありませんfoo)。ライブラリをlibfoo.aにリンクしないと、次のようになります。

$> nm libmine.so
0000000000000a78 T bar
                 U foo

したがって、私のライブラリは のバージョンを使用しますがfoo、これも望ましくありません。私が欲しいのは:

$> nm libmine.so
0000000000000a78 T bar

コンパイルfoo時にリンクされ、そのシンボルはエクスポートされません。

最小限の例

これを読む必要はありませんが、これを使って遊んで解決策を見つけることができます。

bar.cpp: コードを持っていないサードパーティのアプリを表します:

#include <iostream>
extern "C" void foo(){ std::cerr << "old::foo" << std::endl; }
extern "C" void bar(){ std::cerr << "old::bar" << std::endl; foo(); }

foo.cpp: 私のライブラリとサードパーティの両方で使用される関数の新しいバージョンを表します:

#include <iostream>
extern "C" void foo(){ std::cerr << "new::foo" << std::endl; }

trap.cpp:私のライブラリからのコード、それはトラップbarし、新しいものを呼び出してfoo転送します:

#include <iostream>
extern "C" {
  #include <dlfcn.h>
}
extern "C" void foo();
extern "C" void bar(){
  std::cerr << "new::bar" << std::endl;
  foo(); // Should be new::foo
  void (*fwd)() = (void(*)())dlsym(RTLD_NEXT, "bar");
  fwd(); // Should use old::foo
}

exec.cpp: 呼び出すダミーの実行可能ファイルbar:

extern "C" void bar();

int main(){
  bar();
}

Makefile: Unix のみ、申し訳ありません

default:
    # The third party library
    g++ -c -o bar.o bar.cpp -fpic
    gcc -shared -Wl,-soname,libext.so -o libext.so bar.o
    # The updated library
    g++ -c -o foo.o foo.cpp -fPIC
    ar rcs libfoo.a foo.o
    # My trapping library
    g++ -c -o trap.o trap.cpp -fPIC
    gcc -shared -Wl,-soname,libmine.so -o libmine.so trap.o -ldl -L. -lfoo
    # The dummy executable
    g++ -o test exec.cpp -L. libext.so

この場合、;をbar呼び出します。foo通常の実行は次のとおりです。

$> ./test
old::bar
old::foo

ライブラリをプリロードすると、interceptsbarが呼び出され、myfooおよび forwardsが呼び出されますbar。現在の実行は次のとおりです。

$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
new::foo

最後の行が間違っています。目的の出力は次のとおりです。

$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo

解決

1) 受け入れられた回答で指摘されているように、リンカー バージョン スクリプトを使用して、望ましくないシンボルのスコープをグローバルからローカルに変更できます。

BAR {
  global: bar;
  local: *;
};

リンカー バージョンのショーでのコンパイルfooはローカルであり、プログラムは期待どおりに動作するようになりました。

$> gcc -shared -Wl,-soname,libmine.so -Wl,--version-script=libmine.version -o libmine.so trap.o -ldl -L. -lfoo
$> nm libmine.so
0000000000000978 T bar
0000000000000000 A BAR  
0000000000000a2c t foo
$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo

libfoo.a2) 別の方法は、属性を使用して再コンパイルし、-fvisibility=hiddenそれに対してリンクすることです。エクスポートされたシンボルの可視性もローカルであり、動作は上記と同じです。

4

1 に答える 1

4

必要なシンボル (barここ)をエクスポートし、他のすべてを非表示にするリンカー バージョン スクリプトを使用します

はこちら

于 2014-03-01T04:20:08.110 に答える