web-dev-qa-db-ja.com

別のsoが必要なsoに対して実行可能ファイルをリンクするときに、ldに-rpath-linkが必要なのはなぜですか?

ここに興味があります。共有オブジェクトを作成しました:

gcc -o liba.so -fPIC -shared liba.c

前のオブジェクトにリンクするもう1つの共有オブジェクト:

gcc -o libb.so -fPIC -shared libb.c liba.so

libb.soにリンクする実行可能ファイルを作成するとき、liba.soが依存していることを発見したときにlibb.soを見つけることができるように、ldに-rpath-linkを指定する必要があります。

gcc -o test -Wl,-rpath-link,./ test.c libb.so

そうでなければ、ldは文句を言います。

なぜtestをリンクするときにldはliba.soを見つけなければならないのですか?私には、ldがliba.soの存在を確認する以外に多くのことをしているようには思えないからです。たとえば、readelf --dynamic ./testを実行すると、必要に応じてlibb.soのみがリストされるため、動的リンカーはlibb.so -> liba.so依存関係を独自に検出し、liba.soを独自に検索する必要があります。

私はx86-64 GNU/Linuxプラットフォームを使用しており、testのmain()ルーチンがlibb.soの関数を呼び出し、それがliba.soの関数を呼び出します。

34
Troels Folke

なぜ、testをリンクするときにldがliba.soを見つけなければならないのですか?私には、ldがliba.soの存在を確認する以外に多くのことをしているようには思えないからです。たとえば、readelf --dynamic ./testを実行すると、必要に応じてlibb.soのみがリストされるため、動的リンカーはlibb.so -> liba.so依存関係を独自に検出し、liba.soを独自に検索する必要があります。

リンクプロセスを正しく理解していれば、ldは実際にはlibb.soでさえ見つける必要はありません。実行時にlibb.soをロードするときに動的リンカーがそれらを解決することを期待して、test内のすべての未解決の参照を無視できます。ただし、ldがこの方法で実行されている場合、リンク時に多くの「未定義の参照」エラーが検出されず、代わりにランタイムでtestをロードしようとしたときに検出されます。したがって、ldは、test自体にないすべてのシンボルがtestに依存する共有ライブラリに実際に見つかるかどうかを追加チェックするだけです。したがって、testプログラムに「未定義の参照」エラー(一部の変数または関数がtest自体ではなく、libb.soでも見つからない場合)は、実行時だけでなくリンク時にも明らかになります。したがって、このような動作は単なる追加の健全性チェックです。

しかし、ldはさらに先へ進みます。 testをリンクすると、ldは、libb.soのすべての未解決の参照が、libb.soが依存する共有ライブラリで見つかることもチェックします(この場合、libb.soliba.soに依存するため、リンクにあるliba.soが必要です)時間)。実際、ldは、libb.soをリ​​ンクしていたときに既にこのチェックを行っています。なぜ2回目にこのチェックを行うのか... ldの開発者は、リンクされたときにロードされる可能性のある古いライブラリに対してプログラムをリンクしようとすると、壊れた依存関係を検出するのに役立つこのダブルチェックを見つけたかもしれませんが、ただし、依存しているライブラリが更新されているため、ロードできません(たとえば、liba.soが後で修正され、一部の関数が削除されました)。

[〜#〜] upd [〜#〜]

わずかな実験を行っただけです。私の仮定libb.soをリ​​ンクしていたときに、実際にldはすでにこのチェックを行っていた」は間違っているようです。

liba.cに次のコンテンツがあると仮定します。

int liba_func(int i)
{
    return i + 1;
}

そしてlibb.cには次のものがあります:

int liba_func(int i);
int liba_nonexistent_func(int i);

int libb_func(int i)
{
    return liba_func(i + 1) + liba_nonexistent_func(i + 2);
}

およびtest.c

#include <stdio.h>

int libb_func(int i);

int main(int argc, char *argv[])
{
    fprintf(stdout, "%d\n", libb_func(argc));
    return 0;
}

libb.soをリ​​ンクする場合:

gcc -o libb.so -fPIC -shared libb.c liba.so

リンカはliba_nonexistent_funcを解決できないエラーメッセージを生成せず、代わりに壊れた共有ライブラリlibb.soをサイレントに生成します。動作は、arで静的ライブラリ(libb.a)を作成する場合と同じですが、生成されたライブラリのシンボルも解決しません。

ただし、testをリンクしようとすると:

gcc -o test -Wl,-rpath-link=./ test.c libb.so

エラーが表示されます:

libb.so: undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status

ldがすべての共有ライブラリを再帰的にスキャンしなかった場合、このようなエラーを検出することはできません。したがって、質問への答えは上で言ったのと同じように見えます:ld needs -rpath-linkリンクされた実行可能ファイルが動的ロードによって後でロードできることを確認するため。ただ健全性チェック。

UPD2

未解決の参照をできるだけ早くチェックするのは理にかなっています(libb.soをリ​​ンクする場合)が、何らかの理由でldはこれを行いません。おそらく共有ライブラリの循環依存関係を作成できるようにするためです。

liba.cには次の実装を含めることができます。

int libb_func(int i);

int liba_func(int i)
{
    int (*func_ptr)(int) = libb_func;
    return i + (int)func_ptr;
}

したがって、liba.solibb.soを使用し、libb.soliba.soを使用します(このようなことは絶対にしないでください)。これは正常にコンパイルされ動作します:

$ gcc -o liba.so -fPIC -shared liba.c
$ gcc -o libb.so -fPIC -shared libb.c liba.so
$ gcc -o test test.c -Wl,-rpath=./ libb.so
$ ./test
-1217026998

readelfliba.solibb.soを必要としないと言っていますが:

$ readelf -d liba.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
$ readelf -d libb.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [liba.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

ldが共有ライブラリのリンク中に未解決のシンボルをチェックした場合、liba.soのリンクはできません。

-rpath-linkの代わりに-rpathキーを使用したことに注意してください。違いは、-rpath-linkはリンク時に最終実行可能ファイルのすべてのシンボルが解決できることを確認するためだけに使用されるのに対し、-rpathは実際にELFにパラメーターとして指定したパスを埋め込むことです:

$ readelf -d test | grep RPATH
 0x0000000f (RPATH)                      Library rpath: [./]

そのため、共有ライブラリ(liba.soおよびlibb.so)が現在の作業ディレクトリ(./)にある場合、testを実行できるようになりました。 -rpath-linkを使用した場合は、test ELFにそのようなエントリはなく、共有ライブラリへのパスを/etc/ld.so.confファイルまたはLD_LIBRARY_PATH環境変数に追加する必要があります。

UPD3

実際には、共有ライブラリのリンク中に未解決のシンボルをチェックすることが可能です。そのためには--no-undefinedオプションを使用する必要があります。

$ gcc -Wl,--no-undefined -o libb.so -fPIC -shared libb.c liba.so
/tmp/cc1D6uiS.o: In function `libb_func':
libb.c:(.text+0x2d): undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status

また、他の共有ライブラリに依存する共有ライブラリのリンクの多くの側面を明確にする良い記事を見つけました: Linuxの二次依存関係の例をよりよく理解する

25
anton_rh

システムは、ld.so.confld.so.conf.d、およびシステム環境LD_LIBRARY_PATHなどを通じて、システム全体ライブラリ検索パスを提供します。標準ライブラリに対してビルドするときに、pkg-config情報などを介してインストールされたライブラリ。ライブラリが定義済みの検索パスにある場合、標準ライブラリ検索パスが自動的にたどられ、必要なすべてのライブラリを見つけることができます。

自分で作成したカスタム共有ライブラリには、標準の実行時ライブラリ検索パスはありません。コンパイルおよびリンク時に-L/path/to/libを指定して、ライブラリへの検索パスを指定します。標準以外の場所にあるライブラリの場合、必要に応じてライブラリ検索パスを実行可能ファイルのヘッダー(ELFヘッダー)に配置して、実行可能ファイルが必要なライブラリを見つけられるようにすることができます。

rpathは、カスタムランタイムライブラリ検索パスをELFヘッダーに埋め込む方法を提供するため、使用するたびに検索パスを指定しなくてもカスタムライブラリを見つけることができます。これは、ライブラリに依存するライブラリにも適用されます。見つけたように、コマンドラインでライブラリを指定する順序が重要であるだけでなく、リンクする各依存ライブラリのランタイムライブラリ検索パスまたはrpath情報も提供する必要があります実行に必要なallライブラリの場所が含まれています。

コメントからの補遺

私の質問は、主にldが「共有ライブラリを自動的に見つけよう」(liba.so)と「リンクに含める」必要がある理由です。

これは、単にldが機能する方法です。 From man ld "共有オブジェクトが必要とする共有オブジェクトリンクに明示的に含まれる場合、-rpathオプションも使用されます... ELF実行可能ファイルのリンク時に-rpathが使用されない場合、環境変数「LD_RUN_PATH」の内容は、定義されている場合に使用されます。あなたの場合、libaLD_RUN_PATHにないため、ldは、実行可能ファイルのコンパイル中にliba(上記)を使用するか、明示的な検索パスを提供することにより、rpathを見つける方法を必要とします。

第二に、「リンクに含める」とは本当に意味します。私にとっては、それは単に「存在を確認する」(liba.soのもの)を意味しているようです、なぜならlibb.soのELFヘッダーは変更されていないため(すでにliba.soに対するNEEDEDタグがありました)、execのヘッダーはlibbを宣言するだけです必要に応じて。 ldがliba.soを見つけることに関心があるのはなぜですか、タスクを実行時リンカーに任せるだけではいけませんか?

いいえ、ldのセマンティクスに戻ります。 "good link"を生成するには、ldall依存ライブラリを見つけられる必要があります。それ以外の場合、ldは適切なリンクを保証できません。実行時リンカーは、find共有ライブラリプログラムが必要だけでなく、find and loadでなければなりません。 ldは、プログラムがリンクされたときにld自体が必要なすべての共有ライブラリを見つけるでない限り、それが起こることを保証できません。

7
David C. Rankin

-rpathオプションと-rpath-linkオプションをいつ使用するかを知る必要があると思います。最初にman ldが指定したものを引用します:

  1. -rpathと-rpath-linkの違いは、-rpathオプションで指定されたディレクトリが実行可能ファイルに含まれ、実行時に使用されるのに対し、-rpath-linkオプションはリンク時にのみ有効であることです。この方法での-rpathの検索は、--with-sysrootオプションで設定されたネイティブリンカーおよびクロスリンカーでのみサポートされます。

リンク時間とランタイムを区別する必要があります。受け入れられたanton_rhの回答によると、未定義シンボルのチェックは、共有ライブラリまたは静的ライブラリをコンパイルおよびリンクする場合は有効になりませんが、実行可能ファイルをコンパイルおよびリンクする場合は有効になります。 (ただし、共有ライブラリと実行可能ファイルであるいくつかのファイルが存在することに注意してください。たとえば、ld.so。これを調べるにはman ld.soと入力します。未定義をチェックするかどうかはわかりませんこれらの「デュアル」種類のファイルをコンパイルすると、シンボルが有効になります)。

したがって、-rpath-linkはリンク時のチェックに使用され、-rpathはリンク時とランタイムに使用されます。これは、rpathがELFヘッダーに埋め込まれているためです。ただし、両方が指定されている場合、リンク時に-rpath-linkオプションが-rpathオプションをオーバーライドすることに注意する必要があります。

それでも、なぜ-rpath-optionおよび-rpathオプションなのですか?それらは「オーバーリンク」を排除するために使用されると思います。こちらをご覧ください 例で解決するLinuxの二次依存関係をよく理解してください。 、単にctrl + Fを使用して、「オーバーリンク」に関連するコンテンツに移動します。 「オーバーリンク」が悪い理由に焦点を当てる必要があり、「オーバーリンク」を避けるために採用する方法のため、ldオプション-rpath-linkおよび-rpathの存在は合理的です。 「オーバーリンク」を回避するために、コンパイルおよびリンク用のコマンドで一部のライブラリを省略します。省略したため、ldはこれらの省略されたライブラリを見つけるために-rpath-linkまたは-rpathを必要とします.

5
Han XIAO

実際にldに通知しているわけではありません(libblibaにリンクする場合)wherelibaは-依存関係であることだけです。クイック ldd libb.soは、libaが見つからないことを示します。

おそらくこれらのライブラリはリンカ検索パスにないため、実行可能ファイルをリンクするときにリンカエラーが発生します。 liba自体をリンクする場合、libbの関数はstill unresolvedですが、ldのデフォルトの動作は、最終実行可能ファイルをリンクするまでDSOの未解決シンボルを気にしないことに注意してください。

1
Mark Nunberg