リンカーは、入力ファイルをコマンド行上に表示された順番に読み取ります。各ファイルは、開かれ、そのファイルの ELF タイプを判別するために検査され、どのように処理する必要があるかが決定されます。リンク編集に必要な入力に適用するファイルタイプは、リンク編集の結合モード、静的または動的のいずれかによって決定されます。
「静的」モードでは、リンカーが入力ファイルとして受け入れるのは、再配置可能オプションまたはアーカイブライブラリだけです。「動的」モードでは、リンカーは、共有オブジェクトも受け入れます。
再配置可能オブジェクトは、リンク編集プロセスへのもっとも基本的な入力ファイルタイプを示しています。これらのファイル内の「プログラムデータ」のセクションは、生成される出力ファイルイメージに連結されます。リンク編集情報のセクションは、あとで使用するために整理されます。新しい情報セクションが生成され、取って代わられるので、情報セクションは出力ファイルイメージの一部にはなりません。シンボルは、内部シンボルテーブルに集められ、検査および解決されます。このテーブルを使用して、出力イメージ内に 1 つ以上のシンボルテーブルが作成されます。
入力ファイルは直接リンク編集コマンド行に指定できますが、アーカイブライブラリと共有オブジェクトは、一般に –l オプションを使用して指定します。追加ライブラリとのリンクを参照してください。リンク編集時のアーカイブライブラリと共有オブジェクトの解釈は、かなり違います。次の 2 つのセクションで、この違いについて説明します。
アーカイブは、ar(1) を使用して構築します。アーカイブは通常、アーカイブシンボルテーブルを持つ再配置可能オブジェクトの集合で構成されます。このシンボルテーブルにより、これらの定義の提供するオブジェクトとシンボル定義との関係がわかります。デフォルトでは、リンカーを使用すると、アーカイブメンバーを選択して抽出できます。リンカーは、未解決のシンボル参照を使用して、アーカイブから結合プロセスの完了に必要なオブジェクトを選択します。1 つのアーカイブのすべてのメンバーを明示的に抽出することもできます。
リンカーは、次の条件で、アーカイブから再配置可能なオブジェクトを抽出します。
アーカイブに、現在リンカーの内部シンボルテーブル内に保持されている、シンボル参照を満たすシンボル定義が入っている場合。この参照は、「未定義」シンボルと呼ばれる場合もあります。
アーカイブに、現在リンカーの内部シンボルテーブル内に保持されている、未確認シンボル定義を満たすデータシンボル定義が入っている場合。この例としては、FORTRAN COMMON ブロック定義があります。これにより、同じ DATA シンボルを定義する再配置可能オブジェクトが抽出されます。
アーカイブのメンバーに、隠された可視性または保護された可視性を必要とする参照に一致するシンボル定義が含まれる場合。表 35 を参照してください。
リンカーの –z allextract が有効になっている場合。このオプションにより、選択式のアーカイブ抽出は中止され、処理中のアーカイブからアーカイブメンバーがすべて抽出されます。
選択式アーカイブ抽出では、–z weakextract オプションが有効になっていないかぎり、ウィークシンボル参照でアーカイブからのオブジェクト抽出は実行されません。詳細は、単純な解決を参照してください。
選択的なアーカイブ抽出によって、リンカーは 1 つのアーカイブで複数のパスを作成します。必要に応じて、リンカー内部のシンボルテーブルに累積されているシンボル情報を満たすために、再配置可能オブジェクトが抽出されます。リンカーが、再配置可能オブジェクトを抽出せずに、アーカイブを通るフルパスを作成すると、次の入力ファイルが処理されます。
アーカイブが検出されたときに必要な再配置可能オブジェクトだけを抽出することから、コマンド行でのアーカイブの位置が重要であることがわかります。コマンド行上のアーカイブの位置を参照してください。
共有オブジェクトは、分割不可能な、1 つまたは複数の入力ファイルの以前の編集によって生成された総体単位です。リンカーが共有オブジェクトを処理すると、共有オブジェクトの全内容は、その結果作成された出力ファイルイメージの論理的な部分になります。この論理的な組み込みは、リンク編集プロセスにとって共有オブジェクト内に定義されたすべてのシンボルエントリが利用可能になることを意味しています。
共有オブジェクトのプログラムデータセクションとほとんどのリンク編集情報セクションは、リンカーでは使用されません。これらのセクションは、共有オブジェクトが結合されて実行可能プロセスが生成されるときに、実行時リンカーによって解釈されます。ただし、共有オブジェクトの生成は記憶されます。このオブジェクトが実行時に利用可能にしなければならない依存関係であることを示す情報が、出力ファイルイメージに保存されます。
デフォルトでは、リンク編集の一部として指定された共有オブジェクトはすべて、構築中のオブジェクト内に依存関係として記録されます。この記録は、そのオブジェクトが、共有オブジェクトによって提供された実際の参照シンボルを生成するかどうかに関係なく実行されます。実行時のリンクのオーバーヘッドを最小限に抑えるために、構築中のオブジェクトからのシンボル参照を解決する依存関係だけを指定してください。リンカーのデバッグ機能、および –u オプションを指定した ldd(1) を使用すると、未使用の依存関係を確認できます。リンカーの –z discard-unused=dependencies オプションを使用すると、使用されていない共有オブジェクトの依存関係の記録を抑制できます。未使用の依存関係の削除も参照してください。
共有オブジェクトに、ほかの共有オブジェクトに対する依存関係がある場合、この依存関係も処理できます。この処理は、すべてのコマンド行入力ファイルが処理されたあと、シンボル解決プロセスを完了するために実行されます。ただし、生成される出力ファイルイメージ内に、共有オブジェクト名は依存関係として記録されません。
コマンド行での共有オブジェクトの位置は、アーカイブ処理の場合ほど重要ではありませんが、その位置は広範囲に影響を及ぼす可能性があります。同じ名前の複数のシンボルを、再配置可能オブジェクトと共有オブジェクト間や複数の共有オブジェクト間に出現させることができます。シンボル解決を参照してください。
リンカーによって処理される共有オブジェクトの順序は、出力ファイルイメージ内に格納された従属情報に保持されます。遅延読み込みがない場合、実行時リンカーは指定された共有オブジェクトを同じ順序で読み込みます。そのため、リンカーと実行時リンカーは、多重に定義された一連のシンボルのうち、1 つのシンボルの最初のエントリを選択します。
通常、コンパイラドライバによって、適切なライブラリがリンカーに指定されているかどうかが確認されますが、ほとんどの場合、自分独自のライブラリを指定することが必要です。共有オブジェクトとアーカイブは、リンカーに対して必要な入力ファイルの名前を明示的につけることで指定できます。ただし、より一般的で柔軟性が高いのは、リンカーの –l オプションを使用する方法です。
規約により、共有オブジェクトは通常、接頭辞 lib と接尾辞 .so によって指定されます。アーカイブは、接頭辞 lib と接尾辞 .a によって指定されます。たとえば、libfoo.so は、コンパイル環境に使用できる foo 実装の共有オブジェクトバージョンです。libfoo.a は、ライブラリのアーカイブバージョンです。
これらの規則は、リンカーの –l オプションによって認識されます。このオプションは、通常、追加ライブラリをリンク編集に供給する場合に使用します。次の例では、リンカーに libfoo.so を検索するように指示します。リンカーが libfoo.so を検索できない場合は、libfoo.a を検索してから次の検索ディレクトリに移動します。
$ cc -o prog file1.c file2.c -lfoo
動的モードでリンク編集を行う場合、共有オブジェクトとアーカイブとを組み合わせたものへのリンクを選択できます。静的モードでリンク編集を行う場合、入力を受け入れるのはアーカイブライブラリだけです。
動的モードで –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 を探します。
コマンド行上のアーカイブの位置は、作成される出力ファイルに影響を及ぼします。リンカーはアーカイブを検索して、以前に参照したことのある定義されていない仮の外部参照だけを解決します。この検索が完了し、必要な再配置可能オブジェクトが抽出された後で、リンカーはコマンド行上の次の入力ファイルに移動します。
このためデフォルトでは、コマンド行上で先行するアーカイブを、後続の入力ファイルからの新しい参照の解決に使用することはありません。たとえば、次のコマンドでは、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-now オプションを指定すると、この処理は簡単になります。–z rescan-now オプションは、コマンド行でこのオプションが検出されると、すぐにリンカーで処理されます。このオプションより前にコマンド行で処理されたすべてのアーカイブは、すぐに再処理されます。この処理は、シンボル参照を解決する追加のアーカイブメンバーの位置を特定しようとします。アーカイブ全体を走査しても新しい再配置可能オブジェクトが抽出されないと、アーカイブの再走査は終了します。前述の例は、次のように単純化できます。
$ cc -o prog .... -lA -lB -lC -z rescan-now
また、–z rescan-start および –z rescan-end のオプションを使用すると、相互に依存するアーカイブを 1 つのアーカイブグループにまとめることができます。結びの区切り文字がコマンド行に書かれていると、これらのグループはリンカーですぐに再処理されます。グループ内で見つかったアーカイブは再処理され、シンボル参照を解決する追加アーカイブメンバーを検出しようとします。このアーカイブ再走査は、渡されたアーカイブグループに新しいメンバーが検出されなくなるまで続けられます。アーカイブグループを使用すると、前の例は次のように書くことができます。
$ cc -o prog .... -z rescan-start -lA -lB -lC -z rescan-end
これまでの例はすべて、コマンド行に指定されたライブラリの検索場所をリンカーが認識していることを前提にしています。デフォルトでは、32 ビットオブジェクトをリンクする場合、リンカーがライブラリを検索するディレクトリとして認識しているのは、2 つの標準的なディレクトリ /lib とそのあとの /usr/lib だけです。64 ビットオブジェクトをリンクする場合は、2 つの標準的なディレクトリ /lib/64 と /usr/lib/64 だけを使用します。これ以外のディレクトリを検索させたい場合には、リンカーの検索パスに明示的に付加する必要があります。
リンカー検索パスを変更するには、コマンド行オプションを使用する方法と、環境変数を使用する方法があります。
–L オプションを使用すると、ライブラリ検索パスに新しいパス名を追加できます。このオプションは、コマンド行上で遭遇したその地点で、検索パスを変更します。たとえば、次のコマンドは、path1、/lib、/usr/lib の順に libfoo を検索します。このコマンドは、path1、path2、/lib、/usr/lib の順に libbar を検索します。
$ cc -o prog main.o -Lpath1 file1.c -lfoo file2.c -Lpath2 -lbar
–L オプションを使用して定義されたパス名は、リンカー専用です。これらのパス名は、作成される出力ファイルイメージには記録されません。したがって、実行時リンカーはこれらのパス名を使用できません。
–Y オプションを使用すると、リンカーが検索するデフォルトのディレクトリを変更できます。このオプションに指定する引数は、ディレクトリのリストをコロンで区切った書式で示します。たとえば、次のコマンドは、ディレクトリ /opt/COMPILER/lib と /home/me/lib 内だけを調べて libfoo を検索します。
$ cc -o prog main.c -YP,/opt/COMPILER/lib:/home/me/lib -lfoo
–Y オプションを使用して指定したディレクトリは、–L オプションを使用して補足できます。多くの場合、コンパイラドライバには、コンパイラ固有の検索パスを指定するための –Y オプションがあります。
環境変数 LD_LIBRARY_PATH を使用しても、リンカーのライブラリ検索パスに付加できます。一般に、LD_LIBRARY_PATH には、コロンで区切られたディレクトリリストを取ります。LD_LIBRARY_PATH のもっとも一般的な書式は、セミコロンで区切られた 2 つのディレクトリリストです。これらのリストは、コマンド行で提供される –Y リストの前後に検索されます。
ここでは、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:/lib:/usr/lib です。
LD_LIBRARY_PATH 定義の一部にセミコロンが指定されてない場合は、指定されたディレクトリリストは、–L オプションのあとで解釈されます。次の例では、有効な検索パスは path1:path2:....:pathn:dir1:dir2:/lib:/usr/lib です。
$ LD_LIBRARY_PATH=dir1:dir2 $ export LD_LIBRARY_PATH $ cc -o prog main.c -Lpath1 .... -Lpath2 .... -Lpathn -lfoo
実行時リンカーは、デフォルトでは 2 つの場所で依存関係を検索します。32 ビットオブジェクトを処理する場合、デフォルトでは /lib と /usr/lib が検索されます。64 ビットオブジェクトを処理する場合、デフォルトでは /lib/64 と /usr/lib/64 が検索されます。このほかのディレクトリを検索する場合は、実行時リンカーの検索パスに明示的に追加する必要があります。
動的実行可能ファイルまたは共有オブジェクトが別の共有オブジェクトとリンクされるとき、これらの共有オブジェクトは依存関係として記録されます。このような依存関係は、プロセスの実行中に実行時リンカーによって再配置される必要があります。動的なオブジェクトをリンクする場合は、出力ファイルに 1 つ以上の検索パスを記録できます。この検索パスは、「実行パス」と呼ばれます。実行時リンカーは、オブジェクトの実行パスを使用して、オブジェクトの依存関係を特定します。
–z nodefaultlib オプションを使用すると、実行時にデフォルトの場所を検索しない特別なオブジェクトを作成できます。このオプションを使用すると、オブジェクトのすべての依存関係はその「実行パス」を使用して検索されます。このオプションがないと、実行時リンカーの検索パスをどのように拡張しても、最後に使用された検索パスが常にデフォルトの場所になります。
コロンで区切られたディレクトリリストを指定する –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.1 と libbar.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 動的ストリングトークンを使用すると、柔軟に実行パスを記録できます。関連する依存関係の配置を参照してください。
動的オブジェクトは、実行時の初期設定と終了処理のためのコードを提供することができます。動的オブジェクトの初期設定コードは、処理中に動的オブジェクトが読み込まれるたびに、1 回ずつ実行されます。動的オブジェクトの終了コードは、動的オブジェクトが処理から読み取り解除されるか、または処理の終了のたびに 1 回ずつ実行されます。このコードは、関数ポインタの配列、または単一コードブロックのうちいずれか 1 つのセクションタイプで組み込まれます。どちらのセクションタイプも、入力再配置可能オブジェクトの同類のセクションを連結して構築されます。
セクション .pre_initarray、.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_INIT と DT_FINI でそれぞれ識別します。これらのタグは関連するセクションを識別して、実行時リンカーによって呼び出されるようにします。
初期設定および終了コードの実行時の詳細は、初期設定および終了ルーチンを参照してください。
初期設定および終了関数の登録をリンカーから直接実行するには、–z initarray オプションと –z finiarray オプションを使用します。たとえば、次のコマンドの結果、foo() のアドレスが .init_array 要素に配置され、bar() のアドレスが .fini_array 要素に配置されます。
$ cat main.c #include <stdio.h> void foo() { (void) printf("initializing: foo()\n"); } void bar() { (void) printf("finalizing: bar()\n"); } void main() { (void) printf("main()\n"); } $ 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(3C) を使ってメモリーをダンプできる動的オブジェクトとともに組み込まれている場合、データの初期設定だけを別個に行なってください。