ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris 11.1 リンカーとライブラリガイド Oracle Solaris 11.1 Information Library (日本語) |
7. システムのパフォーマンスを最適化するオブジェクトの構築
パート IV ELF アプリケーションバイナリインタフェース
リンカーは、入力ファイルをコマンド行上に表示された順番に読み取ります。各ファイルは、開かれ、そのファイルの ELF タイプを判別するために検査され、どのように処理する必要があるかが決定されます。リンク編集に必要な入力に適用するファイルタイプは、リンク編集の結合モード、「静的」または「動的」のいずれかによって決定されます。
「静的」モードでは、リンカーが入力ファイルとして受け入れるのは、再配置可能オプションまたはアーカイブライブラリだけです。「動的」モードでは、リンカーは、共有オブジェクトも受け入れます。
再配置可能オブジェクトは、リンク編集プロセスへのもっとも基本的な入力ファイルタイプを示しています。これらのファイル内の「プログラムデータ」のセクションは、生成される出力ファイルイメージに連結されます。リンク編集情報のセクションは、あとで使用するために整理されます。新しい情報セクションが生成され、取って代わられるので、情報セクションは出力ファイルイメージの一部にはなりません。シンボルは、内部シンボルテーブルに集められ、検査および解決されます。このテーブルを使用して、出力イメージ内に 1 つ以上のシンボルテーブルが作成されます。
入力ファイルはリンク編集コマンド行に直接指定できますが、アーカイブライブラリと共有オブジェクトは通常、-l オプションを使って指定します。「追加ライブラリとのリンク」を参照してください。リンク編集時のアーカイブライブラリと共有オブジェクトの解釈は、かなり違います。次の 2 つのセクションで、この違いについて説明します。
アーカイブは、ar(1) を使用して構築されます。アーカイブは通常、アーカイブシンボルテーブルを持つ再配置可能オブジェクトの集合で構成されます。このシンボルテーブルにより、これらの定義の提供するオブジェクトとシンボル定義との関係がわかります。デフォルトでは、リンカーを使用すると、アーカイブメンバーを選択して抽出できます。リンカーは、未解決のシンボル参照を使用して、アーカイブから結合プロセスの完了に必要なオブジェクトを選択します。1 つのアーカイブのすべてのメンバーを明示的に抽出することもできます。
リンカーがアーカイブから再配置可能オブジェクトを抽出するのは、次のような場合です。
アーカイブに、現在リンカーの内部シンボルテーブル内に保持されている、シンボル参照を満たすシンボル定義が入っている場合。この参照は、「未定義」シンボルと呼ばれる場合もあります。
アーカイブに、現在リンカーの内部シンボルテーブル内に保持されている、未確認シンボル定義を満たすデータシンボル定義が入っている場合。この例としては、FORTRAN COMMON ブロック定義があります。この定義により、同じ DATA シンボルを定義する再配置可能オブジェクトが抽出されます。
アーカイブのメンバーに、隠された可視性または保護された可視性を必要とする参照に一致するシンボル定義が含まれる場合。表 12-21 を参照してください。
リンカーの -z allextract が実行された場合。このオプションにより、選択式のアーカイブ抽出は中止され、処理中のアーカイブからアーカイブメンバーがすべて抽出されます。
選択式アーカイブ抽出では、-z weakextract オプションが有効になっていないかぎり、ウィークシンボル参照でアーカイブからのオブジェクト抽出は実行されません。詳細は、「単純な解決」を参照してください。
注 - オプション -z weakextract、-z allextract、および -z defaultextract を使用すると、複数のアーカイブ間でアーカイブ抽出メカニズムを切り替えることができます。
選択的なアーカイブ抽出によって、リンカーは 1 つのアーカイブで複数のパスを作成します。必要に応じて、リンカー内部のシンボルテーブルに累積されているシンボル情報を満たすために、再配置可能オブジェクトが抽出されます。リンカーが、再配置可能オブジェクトを抽出せずに、アーカイブを通るフルパスを作成すると、次の入力ファイルが処理されます。
アーカイブが検出されたときに必要な再配置可能オブジェクトだけを抽出することから、コマンド行でのアーカイブの位置が重要であることがわかります。「コマンド行上のアーカイブの位置」を参照してください。
注 - リンカーはアーカイブで複数のパスを作成してシンボルを解決しますが、このメカニズムはかなり負担が大きいものです。特に再配置可能オブジェクトのランダムな組織を含む大きなアーカイブでは、負担が大きくなります。この場合は、lorder(1) や tsort(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
注 - 命名規約には、共有オブジェクトのコンパイル環境での使用に関するものと、共有オブジェクトの実行時環境での使用に関するものがあります。コンパイル環境では、単に .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 を探します。
コマンド行上のアーカイブの位置は、作成される出力ファイルに影響を及ぼします。リンカーはアーカイブを検索して、以前に参照したことのある定義されていない仮の外部参照だけを解決します。この検索が完了し、必要な再配置可能オブジェクトが抽出された後で、リンカーはコマンド行上の次の入力ファイルに移動します。
このためデフォルトでは、コマンド行上で先行するアーカイブを、後続の入力ファイルからの新しい参照の解決に使用することはありません。たとえば、次のコマンドでは、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 オプションを使用して定義されたパス名は、リンカー専用です。これらのパス名は、作成される出力ファイルイメージには記録されません。したがって、実行時リンカーはこれらのパス名を使用できません。
注 - カレントディレクトリ内のライブラリの検索にリンカーを使用する場合は、-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
注 - この環境変数は、実行時リンカーの検索パスを増強する場合にも使用できます。「実行時リンカーが検索するディレクトリ」を参照してください。この環境変数がリンカーに影響しないようにするには、-i オプションを使用します。
実行時リンカーは、デフォルトでは 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 動的ストリングトークンを使用すると、柔軟に実行パスを記録できます。「関連する依存関係の配置」を参照してください。
注 - 以前は、-R オプションの指定に代わる方法として、環境変数 LD_RUN_PATH を設定し、それをリンカーが使用できるようにする方法がありました。LD_RUN_PATH および -R の適用範囲と機能はまったく同じですが、この両方を指定した場合は、-R が LD_RUN_PATH より優先されます。
動的オブジェクトは、実行時の初期設定と終了処理のためのコードを提供することができます。動的オブジェクトの初期設定コードは、処理中に動的オブジェクトが読み込まれるたびに、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) を使ってメモリーをダンプできる動的オブジェクトとともに組み込まれている場合、データの初期設定だけを別個に行なってください。