リンカーとライブラリ

入力ファイルの処理

リンカーは、入力ファイルをコマンド行上に表示された順番に読み取ります。各ファイルは、オープンされ、その ELF ファイルタイプを判別するために検査され、どのように処理する必要があるかが決定されます。リンク編集に必要な入力に適用するファイルタイプは、リンク編集の結合モード、「静的」または「動的」のいずれかによって決定されます。

「静的」方法では、リンカーが入力ファイルとして受け入れるのは、再配置可能オプションまたはアーカイブライブラリだけです。「動的」方法では、リンカーは、共有オブジェクトも受け入れます。

再配置可能オブジェクトは、リンク編集プロセスへの最も基本的な入力ファイルタイプを示しています。これらのファイル内の「プログラムデータ」のセクションは、生成される出力ファイルイメージに結合されます。「リンク編集情報」のセクションは、今後の利用のために構成されますが、出力ファイルイメージには組み込まれません。それは、これを配置する場所として、新しいセクションが生成されるからです。シンボルは、内部シンボルテーブルに集められ、検査および解決されます。このテーブルを使用して、出力イメージ内に 1 つ以上のシンボルテーブルが作成されます。

入力ファイルは、リンク編集のコマンド行上に直接指定できますが、アーカイブライブラリと共有オブジェクトは通常、 -l オプションを使用して指定します。このメカニズムおよび 2 種類のリンクモードとこのメカニズムとの関連については、追加ライブラリとのリンク を参照してください。ただし、通常、共有オブジェクトが共有ライブラリと呼ばれ、さらに、これら 2 つのオブジェクトを同じオプションを使用して指定したとしても、共有オブジェクトとアーカイブライブラリは全く別のものです。次の 2 つの項で、この違いについて説明します。

アーカイブ処理

アーカイブは、ar(1) を使用して構築され、通常、アーカイブシンボルテーブルとともに再配置可能オブジェクトの集合で構成されます。このシンボルテーブルにより、これらの定義の提供するオブジェクトとシンボル定義との関係がわかります。デフォルトでは、リンカーを使用すると、アーカイブ構成要素を選択して抽出できます。リンカーがアーカイブを読み取る場合は、結合処理を完了させるために必要なアーカイブから、オブジェクトだけを選択するように作成された内部シンボルテーブル内の情報を使用します。1 つのアーカイブのすべての構成要素を明示的に抽出することもできます。

次のような場合に、リンカーはアーカイブから再配置可能オブジェクトを抽出します。

選択式アーカイブ抽出においては、ウィークシンボル参照では、-z weakextract オプションが発効されていない限り、アーカイブからの抽出は実行されません。詳細は、単純な解決を参照してください。


注 –

オプション -z weakextract-z allextract、および -z defaultextract により、複数のアーカイブ間でアーカイブメカニズムを切り替えることができます。


選択式アーカイブ抽出の場合、リンカーは、リンカーの内部シンボルテーブル内に蓄積されたシンボル情報を満たすために必要な、再配置可能オブジェクトを抽出するアーカイブを通る複数のパスを作成します。リンカーが、再配置可能オブジェクトを抽出せずに、アーカイブを通る完全なパスを作成すると、リンカーは次の入力ファイルの処理に移ります。

アーカイブが検出されたときに必要な再配置可能オブジェクトだけをアーカイブから抽出することから、入力ファイルリスト内でのアーカイブの位置が重要であることがわかります。コマンド行上のアーカイブの位置 を参照してください。


注 –

リンカーは、アーカイブを通る複数のパスを作成し、シンボルを解決しますが、このメカニズムは、ランダムに構成された再配置可能オブジェクトが組み込まれた大容量のアーカイブの場合には、非常にコストがかかります。このような場合は、lorder(1)tsort(1) などのツールを使用してアーカイブ内の再配置可能オブジェクトを配列し、リンカーが実行しなければならないパスの数を削減することができます。


共有オブジェクトの処理

共有オブジェクトは、分割不可能な、1 つまたは複数の入力ファイルの以前の編集によって生成された総体単位です。リンカーが共有オブジェクトを処理すると、共有オブジェクトの全内容は、その結果作成された出力ファイルイメージの論理的な部分になります。この論理的な組み込みは、リンク編集プロセスにとって共有オブジェクト内に定義されたすべてのシンボルエントリが利用可能になることを意味しています。共有オブジェクトは、プロセスの実行中に物理的にコピーされます。

共有オブジェクトのプログラムデータセクションとほとんどのリンク編集情報セクションは、リンカーでは使用されません。これらのセクションは、共有オブジェクトが結合されて実行可能プロセスが生成されるときに、実行時リンカーによって解釈されます。ただし、共有オブジェクトのエントリが記憶され、情報は出力ファイルイメージ内に格納されて、このオブジェクトには依存関係があり、実行時に使用可能にする必要があるかどうかが指示されます。

デフォルトでは、リンク編集の一部として指定された共有オブジェクトはすべて、作成されるオブジェクト内に依存関係として記録されます。この記録は、そのオブジェクトが、共有オブジェクトによって提供された実際の参照シンボルを生成するかどうかに関係なく実行されます。実行時リンクのオーバヘッドを最小限にするには、作成されたオブジェクトからシンボル参照を解決するために必要な依存関係だけを、リンク編集の一部として指定します。リンカーのデバッギング機能および -u オプションを指定した ldd(1) を使用して、使用されない依存関係を確認することができます。または、リンカーの -z ignore オプションを使用すると、使用しない共有オブジェクトの依存関係の記録を抑制できます。

共有オブジェクトに、他の共有オブジェクトに対する依存関係がある場合、この依存関係も処理されます。この処理は、すべてのコマンド行入力ファイルの処理が終了した後で実行されます。これらの共有オブジェクトは、シンボル解決プロセスを完了するために使用されます。ただし、生成される出力ファイルイメージ内に、これらの名前は依存関係として記録されません。

リンク編集コマンド行上の共有オブジェクトの位置は、アーカイブ処理のための位置に比べるとそれほど重要ではありませんが、大域な効力を持たせることができます。複数のシンボルに同じ名前を付けると、再配置可能オブジェクトと共有オブジェクト間や複数の共有オブジェクト間に出現させることができます。シンボル解決 を参照してください。

リンカーによって処理される共有オブジェクトの順序は、出力ファイルイメージ内に格納された従属情報に保持されます。実行時リンカーがこの情報を読み取るため、指定された共有オブジェクトは同じ順序で読み込まれます。そのため、リンカーと実行時リンカーは、多重に定義された一連のシンボルのうち、1 つのシンボルの最初のエントリを選択します。


注 –

複数のシンボル定義と、他のシンボル用に 1 つのシンボル定義の割り込みを説明した情報は、-m オプションを使用して生成されたロードマップ出力内に報告されます。


追加ライブラリとのリンク

通常、コンパイラドライバによって、適切なライブラリがリンカーに指定されているかどうかが確認されますが、ほとんどの場合、自分独自のライブラリを指定することが必要です。共有オブジェクトとアーカイブは、リンカーに必要な入力ファイルに明示的に命名することによって指定できますが、より一般的で柔軟性のある方法として、リンカーの -l オプションを使用する方法があります。

ライブラリの命名規約

規則により、通常、共有オブジェクトは接頭辞 lib と接尾辞 .so で指定され、アーカイブは接頭辞 lib と接尾辞 .a で指定されます。たとえば、libc.so とは、コンパイル環境で使用可能になった標準 C ライブラリの共有オブジェクトバージョンで、libc.a とは、そのライブラリのアーカイブバージョンです。

これらの規則は、リンカーの -l オプションによって認識されます。このオプションは、通常、追加ライブラリをリンク編集に供給する場合に使用します。次の例では、リンカーに libfoo.so を検索するように指示します。 リンカーが libfoo.so を検索できない場合は、libfoo.a を検索してから次の検索ディレクトリに移動するように指示しています。


$ cc -o prog file1.c file2.c -lfoo

注 –

命名規約には、共有オブジェクトの「コンパイル」環境での使用に関するものと、共有オブジェクトの実行時環境での使用に関するものがあります。コンパイル環境では、単に .so 接尾辞を使用するのに対し、実行時環境では、通常、追加のバージョン番号を指定した接尾辞を使用します。命名規約、および バージョン管理ファイル名の管理を参照してください。


動的モードでリンク編集を行う場合、共有オブジェクトとアーカイブとを組み合わせたものへのリンクを選択できます。静的モードでリンク編集を行う場合、入力を受け入れるのはアーカイブライブラリだけです。

動的モードで、ライブラリの検索を可能にする -l オプションを使用すると、リンカーは、まず、指定されたディレクトリ内で、指定された名前と一致する共有オブジェクトを検索します。一致するものが見つからない場合、リンカーは、次に同じディレクトリ内でアーカイブライブラリを検索します。静的モードで -l オプションを使用する場合は、アーカイブライブラリだけが検索されます。

共有オブジェクトとアーカイブとの混合体へのリンク

ライブラリ検索メカニズムにより動的モードで共有オブジェクトを検索する場合、指定したディレクトリがまず検索され、次にアーカイブライブラリが検索されます。検索タイプをより詳細に制御するには、-B オプションを使用します。

コマンド行上に -B dynamic-B static オプションを必要な回数だけ指定することによって、ライブラリ検索は共有オブジェクトまたはアーカイブをそれぞれ切り替えることができます。たとえば、アーカイブ libfoo.a と共有オブジェクト libbar.so とリンクするには、次のコマンドを発行します。


$ cc -o prog main.o file1.c -Bstatic -lfoo -Bdynamic -lbar

キーワード -B static-B dynamic は、正確には対称ではありません。-B static を指定すると、リンカーは、次の-B dynamic の発生まで入力として共有オブジェクトを受け入れません。しかし、-B dynamic を指定すると、リンカーは、指定されたディレクトリ内で、最初に共有オブジェクトを検索し、次にアーカイブを検索します。

上記の例をより正確に説明すると、リンカーは、最初に libfoo.a を検索し、次に libbar.so を検索します。そしてこれに失敗すると libbar.a を検索します。 最後に、libc.so を検索し、これに失敗すると libc.a を検索します。

コマンド行上のアーカイブの位置

コマンド行上のアーカイブの位置は、作成される出力ファイルに影響を及ぼします。リンカーはアーカイブを検索して、以前に参照したことのある定義されていない仮の外部参照だけを解決します。この検索が完了し、必要な再配置可能オブジェクトが抽出された後で、リンカーはコマンド行上の次の入力ファイルに移動します。

このためデフォルトでは、コマンド行上で先行するアーカイブを、後続の入力ファイルからの新しい参照の解決に使用することはありません。たとえば、次のコマンドでは、file1.c で得たシンボル参照を解決するためだけに、libfoo.a を検索するように、リンカーに指示しています。libfoo.a アーカイブは、file2.c または file3.c のシンボル参照を解決するためには、使用されません。


$ cc -o prog file1.c -Bstatic -lfoo file2.c file3.c -Bdynamic

注 –

原則として、コマンド行の最後にアーカイブを指定するのが最善の方法です。ただし、複数の定義が衝突するために必要となる場合は除きます。


場合によっては、あるアーカイブから抽出された構成要素が、他のアーカイブから抽出された構成要素によって解決されるといった、アーカイブの相互依存関係が存在します。依存関係が循環している場合は、前方の参照を解決するために、コマンド行上でアーカイブを繰り返し指定する必要があります。次に例を示します。


$ cc -o prog .... -lA -lB -lC -lA -lB -lC -lA

アーカイブの繰り返し指定の決定と管理はやっかいなものです。-z rescan オプションを指定すれば、この処理は簡単になります。 このオプションを指定した場合は、すべての入力ファイルの処理が完了すると、シンボル参照の解決に必要な再配置可能オブジェクトを検索するために、すべてのアーカイブが再度処理されます。 アーカイブ全体を走査しても新しい再配置可能オブジェクトが抽出されないと、アーカイブの再走査は終了します。前述の例は、次のように単純化できます。


$ cc -o prog -z rescan .... -lA -lB -lC

リンカーが検索するディレクトリ

ここまでの例はすべて、リンカーが、コマンド行上にリストされたライブラリを検索する場所を認識していることを前提としています。デフォルトでは、32 ビットオブジェクトをリンクする場合、リンカーがライブラリを検索するディレクトリとして認識しているのは、2 つの標準的なディレクトリ /usr/ccs/lib/usr/lib です。64 ビットオブジェクトのリンクの場合は、1 つの標準的なディレクトリ /usr/lib/64 のみが使用されます。これ以外のディレクトリを検索させたい場合には、リンカーの検索パスに明示的に付加する必要があります。

リンカー検索パスを変更するには、2 種類の方法があります。コマンド行オプションを使用するか、環境変数を使用します。

コマンド行オプションの使用

-L オプションを使用すると、ライブラリ検索パスに新しいパス名を追加できます。このオプションは、コマンド行上で遭遇したその地点で、検索パスに影響を与えます。たとえば、次のコマンドでは、libfoo を検出するときは、path1 を検索し、次に /usr/ccs/lib/usr/lib を検索します。 libbar を検出するときは、path1 を検索し、次に path2 を検索し、最後に /usr/ccs/lib/usr/lib を検索します。


$ cc -o prog main.o -Lpath1 file1.c -lfoo file2.c -Lpath2 -lbar

-L オプションを使用して定義されたパス名は、リンカー専用です。これらのパス名は、実行時リンカーが使用するために作成される出力ファイルイメージ内には記録されません。


注 –

カレントディレクトリ内のライブラリの検索にリンカーを使用する場合は、-L を指定する必要があります。ピリオド (.) を使用して、カレントディレクトリを示すことができます。


-Y オプションを使用すると、リンカーが検索するデフォルトのディレクトリを変更できます。このオプションに指定する引数は、ディレクトリのリストをコロンで区切った書式で示します。たとえば、次のコマンドは、ディレクトリ /opt/COMPILER/lib/home/me/lib 内だけを調べて libfoo を検索します。


$ cc -o prog main.c -YP,/opt/COMPILER/lib:/home/me/lib -lfoo

-Y オプションを使用して指定したディレクトリは、-L オプションを使用して補足できます。

環境変数の使用

コロンで区切られたディレクトリリストをとる環境変数 LD_LIBRARY_PATH を使用しても、リンカーのライブラリ検索パスを付加できます。この最も一般的な書式 LD_LIBRARY_PATH では、セミコロンで区切られた 2 つのディレクトリリストをとります。最初のリストは、コマンド行上に指定されたリストよりも前に検索され、2 番目のリストはコマンド行上のリストよりも後に検索されます。

ここでは、LD_LIBRARY_PATH の設定と、いくつかの -L オプションを指定したリンカーの呼び出しを組み合わせています。


$ LD_LIBRARY_PATH=dir1:dir2;dir3
$ export LD_LIBRARY_PATH
$ cc -o prog main.c -Lpath1 ... -Lpath2 ... -Lpathn -lfoo

有効な検索パスは、次のとおりです。dir1:dir2:path1:path2... pathn:dir3:/usr/ccs/lib:/usr/lib

LD_LIBRARY_PATH 定義の一部にセミコロンが指定されてない場合は、指定されたディレクトリリストは、-L オプションの後で解釈されます。次の例では、有効な検索パスは次のとおりです。path1:path2... pathn:dir1:dir2:/usr/ccs/lib:/usr/lib


$ LD_LIBRARY_PATH=dir1:dir2
$ export LD_LIBRARY_PATH
$ cc -o prog main.c -Lpath1 ... -Lpath2 ... -Lpathn -lfoo

注 –

この環境変数は、実行時リンカーの検索パスを拡張する場合にも使用できます。実行時リンカーが検索するディレクトリを参照してください。 この環境変数がリンカーに影響しないようにするには、-i オプションを使用します。


実行時リンカーが検索するディレクトリ

実行時リンカーが依存関係を調べるときに検索する場所は、デフォルトの 1 か所だけです。この場所は、32 ビットオブジェクトを処理する場合は /usr/lib、64 ビットオブジェクトを処理する場合は /usr/lib/64 です。この他のディレクトリを検索する場合は、実行時リンカーの検索パスに明示的に追加する必要があります。

動的実行可能ファイルまたは共有オブジェクトが別の共有オブジェクトとリンクされるとき、これらの共有オブジェクトは依存関係として記録されます。このような依存関係は、プロセスの実行中に実行時リンカーによって再配置される必要があります。リンク編集中には、1 つまたは複数の検索パスを出力ファイル内に記録できます。これらの検索パスは、実行時リンカーが依存関係を検索するときに使用されます。この記録された検索パスは、「実行パス」と呼ばれます。

-z nodefaultlib オプションを使用すると、実行時にデフォルトの場所を検索しない特別なオブジェクトを作成できます。 このオプションを使用すると、オブジェクトのすべての依存関係はその「実行パス」を使用して検索されます。このオプションを使用しない場合、どれだけ検索パスを実行時リンカーに指定しても、最後の要素は常にデフォルトの場所になります。つまり、32 ビットオブジェクトの場合は /usr/lib、64 ビットオブジェクトの場合は /usr/lib/64 です。


注 –

デフォルトの検索パスは、実行時構成ファイルを使って管理できます。デフォルトの検索パスの設定を参照してください。 ただし、オブジェクト作成者はこのファイルの存在に依存すべきではありません。常に、「実行パス」またはデフォルトの場所だけでオブジェクトの依存関係を検索できるようにする必要があります。


コロンで区切られたディレクトリリストを指定する、-R オプションを使用すると、動的実行可能ファイルまたは共有オブジェクト内に「実行パス」を記録できます。次の例では、動的実行可能ファイル prog 内に、「実行パス」 /home/me/lib:/home/you/lib が記録されます。


$ cc -o prog main.c -R/home/me/lib:/home/you/lib -Lpath1 \
-Lpath2 file1.c file2.c -lfoo -lbar

共有オブジェクトの依存関係を取得するとき、実行時リンカーはまず上記パスを検索してから、デフォルトの場所を検索します。この場合、この「実行パス」は、libfoo.so.1libbar.so.1 の検索に使用されます。

リンカーには複数の -R オプションを指定できます。複数指定された場合は、コロンで区切って連結されます。したがって、上記の例は次のように示すこともできます。


$ cc -o prog main.c -R/home/me/lib -Lpath1 -R/home/you/lib \
-Lpath2 file1.c file2.c -lfoo -lbar

さまざまな場所にインストールされる可能性のあるオブジェクトについては、$ORIGIN 動的ストリングトークンを使用して、柔軟に「実行パス」を記録できます。関連する依存関係の配置を参照してください。


注 –

以前は、-R オプションの指定に代わるものとして、環境変数 LD_RUN_PATH を設定してリンカーがこれを使用できるようにする方法がありました。LD_RUN_PATH および -R の適用範囲と機能は全く同じですが、この両方を指定した場合は、-R によって LD_RUN_PATH は上書きされます。


初期設定および終了セクション

動的オブジェクトは、実行時の初期設定と終了処理のためのコードを提供することができます。このコードは、関数ポインタの配列、または単一コードブロックのうちいずれか 1 つのセクションタイプで組み込まれます。どちらのセクションタイプも、入力再配置可能オブジェクトの同類のセクションを連結して構築されます。

.preinit_array.init_array、および .fini_array セクションは、それぞれ実行時の「初期設定前」、初期設定、および終了関数の配列を提供します。動的オブジェクトを作成する際、リンカーはこれらの配列を .dynamic タグペアである DT_PREINIT_[ARRAY/ARRAYSZ]DT_INIT_[ARRAY/ARRAYSZ]、および DT_FINI_[ARRAY/ARRAYSZ] でそれぞれ識別します。これらのタグは関連するセクションを識別して、実行時リンカーによって呼び出されるようにします。「初期設定前」の配列は、動的実行可能ファイルにのみ適用可能です。

.init.fini セクションは、それぞれ実行時の初期設定と終了時のコードブロックを提供します。ただし、通常コンパイラドライバは、入力ファイルリストの冒頭部分と末尾に付加するファイルを使用して .init.fini セクションを供給します。これらのファイルには、.init および .fini コードを個々の関数としてカプセル化する効果があります。これらの関数は、予約シンボル名 _init_fini によりそれぞれ識別されます。 動的オブジェクトを作成する際、リンカーはこれらのシンボルを .dynamic タグの DT_INITDT_FINI でそれぞれ識別します。これらのタグは関連するセクションを識別して、実行時リンカーによって呼び出されるようにします。

初期設定および終了コードの実行の詳細は、初期設定および終了ルーチンを参照してください。

初期設定および終了関数の登録は、-z initarray および -z finiarray オプションを使用してリンカーで直接実行できます。たとえば、次のコマンドの結果、関数 foo() のアドレスが .initarray 要素に配置され、関数 bar() のアドレスが .finiarray 要素に配置されます。


$ cat main.c
#include    <stdio.h>

void foo()
{
        (void) printf("initializing: foo()\n");
}

void bar()
{
        (void) printf("finalizing: bar()\n");
}

main()
{
        (void) printf("main()\n");
        return (0);
}

$ cc -o main -zinitarray=foo -zfiniarray=bar main.c
$ main
initializing: foo()
main()
finalizing: bar()

初期設定および終了セクションの作成は、アセンブラを使用して直接実行できます。しかし、ほとんどのコンパイラは、その宣言を単純化するための特別なプリミティブを提供しています。たとえば、上記のコード例は、次に示す #pragma 定義を使用して書き直すことができます。これらの定義の結果、foo() に対する呼び出しが .init セクション内に配置され、bar() に対する呼び出しが .fini セクション内に配置されます。


$ cat main.c
#include    <stdio.h>

#pragma init (foo)
#pragma fini (bar)

.......
$ cc -o main main.c
$ main
initializing: foo()
main()
finalizing: bar()

初期設定コードと終了コードが複数の再配置可能オブジェクトに分散されると、アーカイブライブラリと共有オブジェクトに組み込まれた場合とで、異なる動作をする可能性があります。アーカイブを使用したアプリケーションのリンク編集は、アーカイブ内の一部オブジェクトしか抽出しない可能性があります。 これらのオブジェクトは、アーカイブのメンバー全体に分散されている初期設定と終了コードの一部しか提供しない可能性があります。そして実行時に、コードのこの部分だけが実行されます。同じアプリケーションを共有オブジェクトを使用して構築した場合は、実行時に依存先が読み込まれると、累積された初期設定コードと終了コードのすべてが実行されます。

実行時にプロセス内で初期設定および終了コードをどのような順序で実行すべきかを判断することは、依存関係の分析を伴う複雑な問題を含んでいます。初期設定および終了コードの内容を制限すると、この分析が簡単になり、柔軟で予測可能な実行時動作が得られます。 詳細は、初期設定と終了の順序を参照してください。

初期設定コードが、 dldump(3DL) を使ってメモリーをダンプできる動的オブジェクトとともに組み込まれている場合、データの初期設定だけを別個に行ってください。