リンカーとライブラリ

再配置処理

実行時リンカーは、アプリケーションが要求する依存関係がすべて配置され、対応付けされると、各オブジェクトを処理し、必要な再配置すべてをを実行します。

オブジェクトのリンク編集中に、入力再配置の可能なオブジェクトとともに提供された再配置の情報が、出力ファイルに適用されます。ただし、動的実行プログラムまたは共有オブジェクトを構築している場合には、ほとんどの再配置は、リンク編集時には完了できません。それは、再配置には、オブジェクトがメモリー内に対応付けされる時だけ入手することができる、論理アドレスが必要だからです。このような場合、リンカーは新しい再配置を出力ファイルイメージの一部として記録し、これが、実行時リンカーが現在処理する必要がある情報になります。

再配置の様々なタイプの詳細については、「再配置型 (プロセッサ固有の) 」を参照してください。ここでは、再配置を 1 つか 2 つのタイプに分けて説明します。

オブジェクトの再配置記録は、dump(1) を使用して表示できます。次に例を示します。


$ dump -rvp libbar.so.1
 
libbar.so.1:
 
.rela.got:
Offset      Symndx                Type              Addend
 
0x10438     0                     R_SPARC_RELATIVE  0
0x1043c     foo                   R_SPARC_GLOB_DAT  0

ここでは、ファイル libbar.so.1 には、「大域オフセットテーブル」(.got セクション) が更新される必要があることを示す、2 つの再配置記録が組み込まれています。

最初の再配置は、単純な相対再配置です。このことは、再配置タイプとシンボルインデックス (Symndx) フィールドがゼロであることから分かります。この再配置では、オブジェクトがメモリーに対応付けされた基底アドレスを使用して、関連する .got オフセットを更新する必要があります。

2 番めの再配置では、シンボル foo のアドレスが必要です。この再配置を完了させるには、実行時リンカーが、これまでに対応付けされた動的実行プログラムと依存関係からこのシンボルを配置する必要があります。

シンボルの検索

オブジェクトがシンボルを必要とする場合、実行時リンカーはそのシンボルを、オブジェクトのシンボル要求の検索範囲と、プロセス内の各オブジェクトによって提供されるシンボルの可視性に基づいて検索します。これらの属性は、読み込まれる時に、オブジェクトのデフォルトとして使用され、dlopen(3X) の特別なモードとしても使用されます。さらに、場合によっては、オブジェクトの構築時に、オブジェクト内に記録されます。

平均的なユーザーであれば、動的実行プログラムとその依存関係、およびdlopen(3X) を通じて入手したオブジェクトに適用されるデフォルトのシンボル検索モードが、理解できるようになります。前者の検索モードについては、この項で概要を説明します。種々のシンボル検索に活用できる後者については、「シンボル検索」で説明しています。

動的実行プログラムと、ともに読み込まれるすべての依存関係には、ワールド検索範囲と、大域シンボル可視性が割り当てられます (「シンボル検索」を参照)。これにより、実行時リンカーが、動的実行プログラムまたはこの実行プログラムとともに読み込まれた依存関係すべてを調べてシンボルを検出する場合、各オブジェクトの検索は、動的実行プログラムから開始され、次にそのオブジェクトが対応付けされた順番でそれぞれの依存関係を検索していきます。

前の項で説明したように、ldd(1) を使用すると、動的実行プログラムの依存関係は対応付けされた順番にリストされます。そのため、共有オブジェクト libbar.so.1 がシンボル foo の再配置を行うためにそのアドレスを必要とし、かつ共有オブジェクトが動的実行プログラム prog: の依存関係である場合には、


$ ldd prog
        libfoo.so.1 =>   /home/me/lib/libfoo.so.1
        libbar.so.1 =>   /home/me/lib/libbar.so.1

実行時リンカーは、foo の検索を、最初に動的実行プログラム prog 内で実行し、次に共有オブジェクト /home/me/lib/libfoo.so.1 内で、最後に共有オブジェクト /home/me/lib/libbar.so.1 内で実行します。


注 -

シンボル検索は、シンボル名のサイズが増大し依存関係の数が増加すると、特に費用のかかる処理になる可能性があります。このパフォーマンスについての詳細は、「性能に関する考慮事項」 で説明しています。


挿入

最初に動的実行プログラム内でシンボルの検索を行い、次に各依存関係内で検索を行うという実行時リンカーのメカニズムは、要求されたシンボルの 最初のオカレンスが、この検索を満足させることを意味しています。そのため、同じシンボルの複数のインスタンスが存在する場合は、最初のインスタンスが、他のすべてのインスタンスに挿入されます (「共有オブジェクトの処理」も参照)。

再配置が実行されるとき

再配置処理を、非シンボルとシンボルの 2 つのタイプに分けて簡素化し、簡単に説明してきましたが、さらに再配置処理の実行時に、これを区別すると便利です。このような、再配置されたオフセットに対して行われる参照のタイプによって、区別が実行されます。参照のタイプは、次のいずれかになります。

データ参照とは、アプリケーションコードによってデータ項目として使用されるアドレスを参照することです。実行時リンカーは、アプリケーションコードに関する知識がないため、このデータ項目がいつ参照されるか認識できません。そのため、データ再配置はすべて、アプリケーションが制御を入手する前の、処理の初期設定中に実行されます。

関数参照とは、アプリケーションコードによって呼び出された関数のアドレスを参照するものです。動的モジュールのコンパイルおよびリンク編集中に、大域関数の呼び出しは再配置されて、プロシージャのリンクテーブルエントリの呼び出しになります (これらのエントリにより、.plt セクションが構成されます)。

プロシージャのリンクテーブルのエントリは、最初に呼び出された制御が実行時リンカーに渡されたときに構成されます (「手続きリンクテーブル (プロセッサに固有)」を参照)。実行時リンカーは、要求されたシンボルを検索しアプリケーション内の情報を書き換えます。そのため、あとに発生する .plt エントリへの呼び出しは、関数に直接送信されます。このメカニズムを使用すると、このタイプの再配置を、関数の最初のインスタンスが呼び出されるまで延期することができます。この処理を、レイジー結合と呼びます。

レイジー結合を実行する、実行時リンカーのデフォルトモードは、空文字以外の値に環境変数 LD_BIND_NOW を設定することにより上書きされます。この環境変数の設定を行うと、実行時リンカーは、プロセスの初期設定中にデータ参照と関数参照の両方の再配置を実行してから、アプリケーションに制御を転送します。次に例を示します。


$ LD_BIND_NOW=yes prog

ここでは、ファイル prog 内とその依存関係の中のすべての再配置は制御がアプリケーションに移る前に処理されることになります。

個々のオブジェクトも、オブジェクトが読み込み時に再配置処理が完了している必要があることを指示するために、リンカーの -z now オプションを使用して構築されます。この再配置の必要条件は、実行時にマークされたオブジェクトの依存関係にも伝達されます。

再配置エラー

最も一般的な再配置エラーは、シンボルが検出されるときに発生します。この状態になると、適切な実行時リンカーのエラーメッセージが表示され、アプリケーションは終了します。次に例を示します。


$ ldd prog
        libfoo.so.1 =>   ./libfoo.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libbar.so.1 =>   ./libbar.so.1
        libdl.so.1 =>    /usr/lib/libdl.so.1
$ prog
ld.so.1: prog: fatal: relocation error: file ./libfoo.so.1: ¥
symbol bar: referenced symbol not found

ここでは、ファイル libfoo.so.1 内で参照されたシンボル bar は、配置できません。


注 -

動的実行プログラムのリンク編集中に、このソートの再配置エラーが起こる可能性は、定義されていない重大なシンボルとしてフラグが付けられます (この例については、「実行可能ファイルの作成」を参照)。この実行時の再配置エラーは、main のリンク編集が、bar のシンボル定義が組み込まれた共有オブジェクト libbar.so.1 の異なったバージョンを使用した場合、または -z nodefs オプションがリンク編集の一部として使用された場合に発生します。


データ参照として使用されたシンボルが配置できないために、このタイプの再配置エラーが発生した場合は、そのエラー状態は、プロセスの初期設定の直後にも発生します。ただし、レイジー結合のデフォルトモードが原因で、関数参照として使用されるシンボルが検出できない場合は、このエラー状態は、アプリケーションが制御を受け取ってから発生します。

後者の場合、コードを実行する実行パスによって、エラー状態が発生するまでに数分または数ヶ月かかる場合もあり、あるいは発生しない場合もあります。この種のエラーを防ぐためには、動的実行プログラムまたは共有オブジェクトの再配置の必要条件を、ldd(1) を使用して、有効にしておきます。

ldd(1) とともに -d オプションを指定すると、すべての依存関係が出力され、すべてのデータ参照の再配置処理が実行されます。データ参照が解析できない場合には、診断メッセージが作成されます。上記の例から、次のように明示されます。


$ ldd -d prog
        libfoo.so.1 =>   ./libfoo.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libbar.so.1 =>   ./libbar.so.1
        libdl.so.1 =>    /usr/lib/libdl.so.1
        symbol not found: bar           (./libfoo.so.1)

ldd(1) とともに -r オプションを指定すると、すべてのデータと関数参照の再配置が処理されます。また、この両方ともが解析されない場合には、診断メッセージが作成されます。