リンカーとライブラリ

再配置処理

アプリケーションが要求する依存関係をすべて読み込んだ後、実行時リンカーは各オブジェクトを処理し、必要な再配置すべてを実行します。

オブジェクトのリンク編集中に、入力再配置の可能なオブジェクトとともに提供された再配置の情報が、出力ファイルに適用されます。ただし、動的実行可能ファイルまたは共有オブジェクトを作成している場合、リンク編集時には再配置の多くを完了できません。これらの再配置には、オブジェクトをメモリーに読み込むときにだけわかる論理アドレスが必要です。このような場合、リンカーは新しい再配置を出力ファイルイメージの一部として記録します。実行時リンカーは、新しい再配置レコードを処理する必要があります。

再配置のさまざまなタイプの詳細については、「再配置型 (プロセッサ固有)」を参照してください。再配置には基本的に 2 つの種類があります。

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


$ elfdump -r libbar.so.1

Relocation Section:  .rel.got:
    type                               offset             section       symbol
  R_SPARC_RELATIVE                    0x10438             .rel.got  
  R_SPARC_GLOB_DAT                    0x1043c             .rel.got      foo

最初の再配置は、単純な相対再配置です。このことは、再配置タイプと、シンボルが参照されていないことからわかります。この再配置では、オブジェクトがメモリーに読み込まれるベースアドレスを使用して、関連する .got オフセットを更新する必要があります。

2 番目の再配置では、シンボル foo のアドレスが必要です。この再配置を完了させるには、実行時リンカーが、これまでに読み込まれた動的実行可能ファイルと依存関係のいずれかを使用して、このシンボルを検出する必要があります。

再配置シンボルの検索

実行時リンカーには、オブジェクトが必要とするシンボルを実行時に検索する責任があります。一般にユーザーは、動的実行可能ファイルやその依存関係および dlopen(3C) によって取得されたオブジェクトに適用される、デフォルトの検索モデルを理解するようになります。しかし、オブジェクトのシンボル属性や特定の結合要件が原因で、より複雑なシンボル検索が行われることもあります。

オブジェクトの 2 つの属性は、シンボル検索に影響を与えます。最初の属性は、要求元オブジェクトのシンボルの検索範囲です。2 つ目の属性は、プロセス内の各オブジェクトによって提供されるシンボルの可視性です。

これらの属性は、オブジェクトを読み込む際、デフォルトとして適用できます。これらの属性は、dlopen(3C) の特定のモードとしても提供できます。場合によっては、これらの属性をオブジェクトの構築時にオブジェクト内に記録することができます。

オブジェクトは、world 検索範囲または group 検索範囲、あるいはその両方を定義できます。

ワールド (world)

オブジェクトは、プロセス内のほかの任意の大域オブジェクト内でシンボルを検索できます。

group

オブジェクトは、同じグループのオブジェクト内のシンボルを検索できます。dlopen(3C) を使用して入手されたオブジェクトから作成された依存関係ツリー、またはリンカーの -B group オプションを使用して構築されたオブジェクトから作成された依存関係ツリーは、固有のグループを形成します。

オブジェクトは、オブジェクトのエクスポートされたシンボルがグローバルに参照可能か、ローカルに参照可能かを定義できます。

大域 (global)

オブジェクトのエクスポートされたシンボルは、ワールド検索範囲を持つ任意のオブジェクトから参照できます。

ローカル (local)

オブジェクトのエクスポートされたシンボルは、同じグループを構成するほかのオブジェクトからのみ参照できます。

もっとも単純な形のシンボル検索については、次の節「デフォルトのシンボル検索」で説明します。一般に、シンボル属性はさまざまな形の dlopen(3C) によって利用されます。これらのシナリオについては、「シンボルの検索」に記載されています。

動的なオブジェクトで直接結合を行うと、別のシンボル検索モデルが提供されます。このモデルでは、実行時リンカーは、リンク編集時に結合されたオブジェクトからシンボルを直接検索します。「直接結合」を参照してください。

デフォルトのシンボル検索

動的実行可能プログラムと、ともに読み込まれるすべての依存関係には、「ワールド」検索範囲と、「大域」シンボル可視性が割り当てられます 。動的実行可能ファイルや、それとともに読み込まれた依存関係を対象としたデフォルトのシンボル検索では、各オブジェクトが検索されます。まず動的実行可能プログラムから検索してから、 オブジェクトが読み込まれた順番に依存関係を検索します。

ldd(1) を使用すると、動的実行可能ファイルの依存関係は読み込まれた順にリストされます。たとえば、動的実行可能ファイル prog で、依存関係として libfoo.so.1libbar.so.1 が指定されているとします。


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

再配置を実行するためにシンボル bar が必要な場合、実行時リンカーはまず bar を動的実行可能ファイル prog の中で検索します。シンボルが見つからない場合、実行時リンカーは共有オブジェクト /home/me/lib/libfoo.so.1 の中を検索し、最後に共有オブジェクト /home/me/lib/libbar.so.1 の中を検索します。


注 –

シンボル検索は、シンボル名のサイズが増大し依存関係の数が増加すると、特にコストのかかる処理になる可能性があります。この性能についての詳細は、「性能に関する考慮事項」で説明しています。これに代わる検索モデルについては、「直接結合」を参照してください。


デフォルトの再配置処理モデルでは、遅延読み込み環境の遷移も提供します。現在読み込まれているオブジェクト内でシンボルが見つからない場合は、そのシンボルを特定するために、保留となっている遅延読み込みオブジェクトが処理されます。この読み込みによって、依存関係を完全には定義していないオブジェクトを補います。ただし、これにより遅延読み込みの利点が失われることがあります。

実行時割り込み

デフォルトで、実行時リンカーはまず動的実行可能プログラム内でシンボルを検索したあと、それぞれの依存関係を検索します。このモデルでは、必要なシンボルが最初に現れた時点で検索条件が満たされます。そのため、同じシンボルの複数のインスタンスが存在する場合は、最初のインスタンスが、ほかのすべてのインスタンスに割り込みます。

シンボル解決がどのように割り込みの影響を受けるかの概要については、「単純な解決」で説明しています。シンボルの可視性を変更し、偶発的な割り込みの可能性を低くするメカニズムは、「シンボル範囲の縮小」で説明しています。

オブジェクトが割り込み処理として明示的に識別されている場合、割り込みをオブジェクト単位で行えます。環境変数 LD_PRELOAD を使ってオブジェクトを読み込むか、リンカーの -z interpose オプションを使ってオブジェクトを作成すると、オブジェクトは割り込み処理として識別されます。実行時リンカーがシンボルを検索する場合、割り込むものとして識別されたオブジェクトはアプリケーションよりもあとで検索されますが、その他の依存関係よりは前に検索されます。

割り込み処理により提供されるすべてのインタフェースの使用が保証されるのは、プロセス再配置が行われる前に割り込み処理が読み込まれる場合のみです。環境変数 LD_PRELOAD を使用して提供される割り込み処理、またはアプリケーションの非遅延読み込み依存関係として確立される割り込み処理は、再配置処理が始まる前に読み込まれます。再配置が始まったあとでプロセスに挿入される割り込み処理は、通常の依存関係に降格されます。割り込み処理を降格できるのは、割り込み処理が遅延読み込みされた場合、または dlopen(3C) を使用した結果として読み込まれた場合です。前者のカテゴリは ldd(1) を使用して検出できます。


$ ldd -Lr prog
        libc.so.1 =>     /lib/libc.so.1
        foo.so.2 =>      ./foo.so.2
        libmapmalloc.so.1 =>     /usr/lib/libmapmalloc.so.1
        loading after relocation has started: interposition request \
                (DF_1_INTERPOSE) ignored: /usr/lib/libmapmalloc.so.1

注 –

遅延読み込みを行うために依存関係を処理している間に、明示的に定義された割り込み処理をリンカーが検出した場合、その割り込み処理は非遅延読み込み可能依存関係として記録されます。


直接結合

直接結合を使用するオブジェクトは、特定のシンボル参照とその定義を提供する依存関係との間の関係を保持します。実行時リンカーは、デフォルトのシンボル検索モデルを使用する代わりに、この情報を使って関連するオブジェクトから直接シンボルを検索します。直接結合情報は、リンク編集で指定される依存関係に対してのみ確立されます。したがって、-z defs オプションを推奨します。

シンボル参照からシンボル定義への直接結合を確立するには、次のいずれかのメカニズムを使用します。

直接結合では、多数のシンボル再配置や依存関係を伴う動的プロセスでのシンボル検索オーバーヘッドを大幅に削減できます。さらに、このモデルでは、同じ名前の複数のシンボルを、それらが直接結合されている個々のオブジェクトから見つけることができます。


注 –

環境変数 LD_NODIRECT をヌル以外の値に設定すれば、実行時に直接結合を無効にできます。


デフォルトのシンボル検索モデルでは、1 つのシンボルへのすべての参照を、1 つの定義に結合することができます。直接結合はデフォルトの検索モデルをバイパスするため、暗黙的な割り込みシンボルを迂回します。ただし、割り込み処理として明示的に識別されているオブジェクトは、シンボル定義を供給するオブジェクトの前に検索されます。明示的な割り込み処理には、環境変数 LD_PRELOAD を使用して読み込まれたオブジェクトや、リンカーの -z interpose オプションを使用して作成されたオブジェクトが含まれます。「実行時割り込み」を参照してください。

デフォルト手法の代替実装を提供するインタフェースがいくつか存在します。これらのインタフェースは、その実装が、プロセス内におけるその手法の唯一のインスタンスであることを前提とします。この例の 1 つに malloc(3C) ファミリがあります。さまざまな malloc() ファミリの実装が存在しますが、各ファミリは、プロセス内で使用される唯一の実装であると想定されています。このようなファミリ内の特定のインタフェースへの直接結合は避けるべきであり、これは、同じプロセスによってその手法のインスタンスが複数参照される可能性があるためです。たとえば、プロセス内の 1 つの依存関係が libc.so.1 に直接結合し、一方で別の依存関係が libmapmalloc.so.1 に直接結合する可能性があります。malloc()free() の異なる 2 つの実装を一貫性のない方法で使用すると、エラーが発生しやすくなります。

プロセス内の唯一のインスタンスであると想定されているインタフェースを提供するオブジェクトは、インタフェースへの直接結合を避けるべきです。特定のインタフェースを、どの呼び出し元からも直接結合できないようにラベル付けするには、次のメカニズムのいずれかを使用します。

非直接ラベル付けを行うと、どのシンボル参照も特定の実装に直接結合できなくなります。参照を解決するためのシンボル検索では、デフォルトのシンボル検索モデルが使用されます。非直接ラベル付けは、Solaris OS に付属するさまざまな malloc() ファミリ実装を構築する目的で採用されました。


注 –

NODIRECT mapfile キーワードは、コマンド行オプション -B direct-z direct と組み合わせることができます。シンボルに NODIRECT を明示的に定義しない場合は、そのシンボルはコマンド行の指令に従います。同様に、DIRECT mapfile キーワードは、コマンド行オプション -B nodirect と組み合わせることができます。シンボルに DIRECT を明示的に定義しない場合は、そのシンボルはコマンド行の指令に従います。


再配置が実行されるとき

再配置は、再配置が実行されるタイミングで 2 つのタイプに区別できます。このような、再配置されたオフセットに対して行われる「参照」のタイプによって、次のように区別されます。

「即時参照」とは、オブジェクトが読み込まれたときにただちに決定しなければならない再配置のことです。この参照は、一般にオブジェクトコードで使用されるデータ項目、関数ポインタ、および位置依存共有オブジェクトからの関数呼び出しに対するものです。即時参照では、再配置された項目が参照されたことを実行時リンカーは認識できません。このため、すべての即時参照は、オブジェクトが読み込まれたら、アプリケーションが制御を獲得または再獲得する前に、再配置が完了する必要があります。

「遅延参照」とは、オブジェクトの実行時に決定できる再配置のことです。通常は、位置独立共有オブジェクトから大域関数への呼び出しか、動的実行可能ファイルから外部関数への呼び出しです。遅延参照を行う動的モジュールをコンパイルおよびリンク編集しているときに、関連付けられた関数呼び出しは、プロシージャーリンクテーブルのエントリへの呼び出しに変換されます。これらのエントリは、.plt セクションを構成します。プロシージャーリンクテーブルの各エントリは、関連付けられた再配置を伴う遅延参照になります。

プロシージャーリンクテーブルの特定のエントリに対する最初の呼び出しの実行中に、制御が実行時リンカーに渡されます。実行時リンカーは、関連付けられたオブジェクト内で必要なシンボルを検索し、エントリ情報を書き換えます。その後のプロシージャーリンクテーブルのエントリへの呼び出しは、直接関数に対して行われます。遅延参照では、関数が最初に呼び出されるまで、再配置を遅延させることができます。この処理は、「遅延」結合と呼ばれることがあります。

実行時リンカーのデフォルトモードは、プロシージャーリンクテーブルの再配置が行われるたびに遅延結合を実行する、というものです。デフォルトモードを無効にするには、環境変数 LD_BIND_NOW にヌル以外の任意の値を設定します。この環境変数の設定により、実行時リンカーは、オブジェクトが読み込まれた時点で、即時参照と遅延参照を両方とも再配置します。これらの再配置は、アプリケーションが制御を獲得または再獲得するまでの間に行われます。たとえば、環境変数を次のように設定して、ファイル prog とその依存関係内のすべての再配置が行われるとします。これらの再配置は、制御がアプリケーションに移る前に行われます。


$ LD_BIND_NOW=1 prog

オブジェクトへのアクセスは、RTLD_NOW として定義されたモードを指定して dlopen(3C) を使用することによっても行えます。リンカーの -z now オプションを使用してオブジェクトを構築すれば、オブジェクトが読み込まれたときに再配置処理を完了させる必要があることを示すことができます。この再配置要件は、実行時に指定したオブジェクトの依存先すべてに波及します。


注 –

前述の即時参照と遅延参照の例は、標準的なものです。ただし、プロシージャーリンクテーブルのエントリの作成は、リンク編集の入力として使用する再配置可能オブジェクトファイルが提供する再配置情報によって、最終的に制御されます。R_SPARC_WPLT30R_386_PLT32 などの再配置レコードには、プロシージャーリンクテーブルのエントリの作成が指定されています。こうした再配置は、位置独立のコードで共通です。

ただし、通常、動的実行可能ファイルは位置に依存するコードから作成されるため、プロシージャーリンクテーブルのエントリが必要であることを示さない場合があります。動的実行可能ファイルの位置は固定されているため、参照が外部関数定義に結合された時点で、リンカーはプロシージャーのリンクテーブルを作成できます。元の再配置レコードに関係なく、このプロシージャーリンクテーブルのエントリを作成できます。


再配置エラー

もっとも一般的な再配置エラーは、シンボルを検出できないときに発生します。この状態になると、適切な実行時リンカーのエラーメッセージが表示され、アプリケーションは終了します。次の例では、ファイル libfoo.so.1 内で参照されたシンボル bar は配置できません。


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

動的実行可能プログラムのリンク編集中に、この種の潜在的な再配置エラーは、定義されていない重大なシンボルとしてフラグが付けられます。例については、「実行可能ファイルの作成」を参照してください。ただし、実行時再配置エラーが発生するのは、実行時に配置される依存関係が、リンク編集の一部として参照される元の依存関係と互換性がない場合です。上記の例では、bar のシンボル定義を含む共有オブジェクト libbar.so.1 のバージョンに対して prog が構築されています。

リンク編集時に -z nodefs オプションを使用すると、オブジェクトの実行時再配置要件の検査が抑制されます。この抑制は、実行時再配置エラーになる可能性があります。

即時参照として使用されたシンボルが検出できないために再配置エラーが発生した場合、そのエラー状態は、プロセスの初期設定中、ただちに発生します。遅延結合のデフォルトモードにより、遅延参照として使用されるシンボルを検出できない場合は、このエラー状態は、アプリケーションが制御を受け取ってから発生します。後者の場合、コードを実行する実行パスによって、エラー状態が発生するまでに数分または数ヶ月かかる場合もあり、あるいは発生しない場合もあります。

この種のエラーを防ぐためには、動的実行プログラムまたは共有オブジェクトの再配置の必要条件を、ldd(1) を使用して有効にします。

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


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

ldd(1)-r オプションを指定すると、すべての即時参照と遅延参照の再配置が処理されます。また、このどちらかの再配置が解決できない場合には、診断メッセージが作成されます。