リンカーに厄介な問題があります。いくつかのシンボルを共有ライブラリから静的ライブラリにリンクしたいが、そのシンボルをエクスポートしたくない (つまり、単純にライブラリをマージしたり とリンクしたりすることはできない--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の関数を呼び出したいことです。言い換えれば、ライブラリのコンパイル時のリンケージが必要です。foo
libfoo.a
libext.so
foo
libext.so
libfoo.a
私が探しているのはlibfoo.a
、シンボルを使用するがエクスポートしないライブラリを定義することです。ライブラリを にリンクするとlibfoo.a
、次のようになります。
$> nm libmine.so
0000000000000a78 T bar
0000000000000b2c T foo
つまり、 と の両方をオーバーロードfoo
しbar
ます ( をオーバーライドしたくありません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.a
2) 別の方法は、属性を使用して再コンパイルし、-fvisibility=hidden
それに対してリンクすることです。エクスポートされたシンボルの可視性もローカルであり、動作は上記と同じです。