リンカーとライブラリ

第 2 章 リンカー

リンク編集プロセスにより、1 つまたは複数の入力ファイルから出力ファイルが作成されます。出力ファイルの作成は、入力ファイルによって提供される入力セクションとともに、リンカーに提供されたオプションによって指示されます。

ファイルはすべて、「実行可能なリンク書式 」(ELF) で表示されます。ELF 書式の詳細については、第 7 章「オブジェクトファイル形式」を参照してください。ただし、ここでの概要説明では、まず、2 つの ELF 構造、「セクション」と「セグメント」について紹介する必要があります。

セクションとは、ELF ファイル内で処理できる、最も小さな、分割できない単位のことです。セグメントとは、セクションの集合で、exec(2) または実行時リンカー ld.so.1(1)でメモリーイメージに対応付けできる最小単位 (これ以上分割できない単位) です。

ELF セクションには多くのタイプがありますが、これらはすべて、リンク編集を基準にして次の 2 つのカテゴリに分類されます。

基本的には、リンカーにより、「プログラムデータセクション」が連結されて出力ファイルになります。「リンク編集情報セクション」は、リンカーによって解釈されて、別のセクションに修正されるか、またはこの後処理される出力ファイルで使用される新しい出力情報セクションが生成されます。

リンカーの、次のような単純な機能の内訳については、この章で説明します。

「セクション」と関連する「セクション」を連結して「セグメント」にするといった連結プロセスは、リンカー内のデフォルト情報を使用して実行されます。通常、ほとんどのリンク編集では、リンカーによって提供されるデフォルトの「セクション」と「セグメント」の処理で十分です。ただしこれらのデフォルトは、対応する mapfile を指定した -M オプションを使用して操作できます。第 9 章「mapfile のオプション」を参照してください。

リンカーの起動

リンカーは、コマンド行から直接実行できます。また、コンパイラドライバを使用して実行することができます。以下の 2 つの節では、この両方の方法を詳しく説明します。ただし、通常は、コンパイラドライバを使用することをお勧めします。コンパイル環境は、多くの場合、コンパイラドライバだけが認識し、頻繁に変化する複雑な操作の連続によって構成されています。

直接起動

リンカーを直接的に起動させる場合は、出力を作成するために必要なすべてのオブジェクトファイルとライブラリを提供する必要があります。リンカーは、出力の作成に使用するつもりのオブジェクトモジュールまたはライブラリに関して、仮説を立てることをしません。たとえば、次のコマンドを発行するとします。


$ ld test.o

この場合、入力ファイルとして test.o だけを使用して、a.out という名前の動的実行可能ファイルを作成します。a.out を有用な実行可能ファイルにするためには、これに初期設定および終了処理コードを組み込む必要があります。このコードは、言語またはオペレーティングシステム固有のもので、通常、コンパイラドライバによって提供されるファイルを通じて提供されます。

また、自分専用の初期設定および終了コードも指定できます。このコードは、実行時リンカーにより正確に認識され、使用できるようにするために、適切に暗号化およびラベル付けを行う必要があります。この暗号化とラベル付けも、コンパイラドライバによって提供されたファイルを通じて提供されます。

実行可能ファイルや共有オブジェクトなどの実行時オブジェクトを作成するときは、コンパイラドライバを使ってリンカーを起動する必要があります。リンカーの直接起動をお勧めするのは、-r オプションを使用して、中間再配置可能オブジェクトを作成する場合だけです。

コンパイラドライバを使用する

リンカーを使用する従来の方法は、言語固有のコンパイラドライバを使用する方法です。アプリケーションを構成する入力ファイルとともに、cc(1)CC(1) などのコンパイラドライバを指定します。すると、コンパイラドライバは、追加ファイルとデフォルトライブラリを追加して、リンク編集を完了させます。これらの追加ファイルは、たとえば、以下のようにコンパイルの呼出しを拡張することによって参照できます。


$ cc -# -o prog main.o
/usr/ccs/bin/ld -dy /opt/COMPILER/crti.o /opt/COMPILER/crt1.o \
/usr/ccs/lib/values-Xt.o -o prog main.o \
-YP,/opt/COMPILER/lib:/usr/ccs/lib:/usr/lib -Qy -lc \
/opt/COMPILER/crtn.o

注 –

この例は、コンパイラドライバによって組み込まれた実際のファイルの例ですが、リンカー起動の表示に使用されるメカニズムによって異なる場合があります。


リンカーオプションの指定

リンカーに対するオプションは、通常、コンパイラドライバのコマンド行を通じて渡されます。コンパイラオプションとリンカーオプションは、ほとんど重複する部分はありません。重複が発生した場合は、通常、特定のオプションをリンカーに渡すことを許可するコマンド行構文が、コンパイラドライバによって提供されます。また、LD_OPTIONS 環境変数を設定して、リンカーにオプションを渡すこともできます。次に例を示します。


$ LD_OPTIONS="-R /home/me/libs -L /home/me/libs" cc -o prog main.c -lfoo

-R および -L オプションがリンカーによって変換され、コンパイラドライバから受信したコマンド行オプションに付加されます。

リンカーは、オプションリスト全体を構文解析し、無効なオプションまたは関連する引数が無効であるオプションを調べます。どちらかの無効なオプションが検索された場合は、該当するエラーメッセージが生成されます。致命的なエラーの場合は、リンカーは強制終了します。次の例では、リンカーの検査により、不当なオプション -X が認識され、-z オプションに不当な引数が検出されています。


$ ld -X -z sillydefs main.o
ld: illegal option -- X
ld: fatal: option -z has illegal argument `sillydefs'

1 つの引数を必要とするオプションが、誤って 2 回指定されている場合には、リンカーは該当する警告を表示しますが、リンク編集は継続します。次に例を示します。


$ ld -e foo ...... -e bar main.o
ld: warning: option -e appears more than once, first setting taken

また、リンカーはオプションリストを調べて重大な不一致も検出します。次に例を示します。


$ ld -dy -a main.o
ld: fatal: option -dy and -a are incompatible

すべてのオプションを処理しても、エラー状態が検出されなかった場合は、次にリンカーは、入力ファイルの処理を行います。

通常使用されるリンカーオプションについては、付録 A 「リンカーのクイックリファレンス」 を参照してください。また、全リンカーオプションの詳細については、ld(1) のマニュアルページを参照してください。

入力ファイルの処理

リンカーは、入力ファイルをコマンド行上に表示された順番に読み取ります。各ファイルは、オープンされ、その 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) を使ってメモリーをダンプできる動的オブジェクトとともに組み込まれている場合、データの初期設定だけを別個に行ってください。

シンボルの処理

入力ファイルの処理中に、入力再配置可能オブジェクトからローカルシンボルが出力ファイルイメージに渡されます。大域シンボルはすべて、リンカーの内部に蓄積されます。再配置可能オブジェクトから大域シンボルが供給されると、この内部シンボルテーブル内が検索されます。過去の入力ファイルで、同じ名前のシンボルに遭遇したことがある場合には、シンボル解決プロセスが呼び出されます。このシンボル解決プロセスは、2 つのエントリのうちどちらを保持するかを決定します。

入力ファイル処理の完了時、シンボル解決中に深刻なエラー状態が発生しなかった場合は、リンカーは、未解決のシンボル参照が残っていないかどうか判別します。未解決のシンボル参照があると、リンク編集は強制終了します。

最後に、リンカーの内部シンボルテーブルが、作成されるイメージのシンボルテーブルに追加されます。

次の項では、シンボル解決と未定義シンボルの処理について詳しく説明します。

シンボル解決

シンボル解決は、簡単で直感的に分かるものから、複雑で当惑するようなものまで、すべての範囲を実行します。解決は、リンカーによって自動的に実行されるか、警告診断プログラムを伴って表示されるか、またはその結果、重大なエラー状態になります。

2 つのシンボルの解決は、シンボルの属性、シンボルを入手したファイルのタイプおよび生成されるファイルのタイプによって異なります。シンボルの属性についての詳細は、シンボルテーブルセクションを参照してください。ただし、以下に説明するシンボルタイプは、識別する価値のある 3 つの基本的なシンボルタイプです。

シンボル解決の最も単純な形では、優先度が比較されます。つまり、定義シンボルは一時的シンボルよりも優先され、一時的シンボルは未定義シンボルよりも優先されます。

次の C コードの例では、これらのシンボルタイプがどのようにして生成されるかを示しています。 未定義シンボルには接頭辞 u_ が、一時的シンボルには接頭辞 t_ が、定義シンボルには接頭辞 d_ がそれぞれ付いています。


$ cat main.c
extern int      u_bar;
extern int      u_foo();

int             t_bar;
int             d_bar = 1;

d_foo()
{
        return (u_foo(u_bar, t_bar, d_bar));
}
$ cc -o main.o -c main.c
$ nm -x main.o

[Index]   Value      Size      Type  Bind  Other Shndx   Name
...............
[8]     |0x00000000|0x00000000|NOTY |GLOB |0x0  |UNDEF  |u_foo
[9]     |0x00000000|0x00000040|FUNC |GLOB |0x0  |2      |d_foo
[10]    |0x00000004|0x00000004|OBJT |GLOB |0x0  |COMMON |t_bar
[11]    |0x00000000|0x00000000|NOTY |GLOB |0x0  |UNDEF  |u_bar
[12]    |0x00000000|0x00000004|OBJT |GLOB |0x0  |3      |d_bar

単純な解決

単純なシンボル解決は、ごく日常的に発生する解決で、類似した特徴を持つ 2 つのシンボルが検出され、一方のシンボルが他方のシンボルよりも優先される場合に実行されます。このシンボル解決は、リンカーによって自動的に実行されます。たとえば、同じ結びつきを伴う複数のシンボルでは、あるファイルから未定義シンボルへの参照は結合されるか、または他のファイルからの定義シンボルまたは一時的シンボル定義によって満たされます。あるいは、あるファイルからの一時的シンボル定義は、他のファイルからの定義シンボルの定義に結合されます。

解決されるシンボルは、大域結合またはウィーク結合されます。ウィーク結合の方が、大域結合よりも優先度が低くなります。そのため、異なる結合を伴うシンボルは、わずかに変更された基本規則に従って解決されます。

ウィークシンボルは通常、個別にあるいは大域シンボルの別名として、コンパイラによって定義されます。この機構では、#pragma 定義を使用します。


$ cat main.c
#pragma weak    bar
#pragma weak    foo = _foo

int             bar = 1;

_foo()
{
        return (bar);
}
$ cc -o main.o -c main.c
$ nm -x main.o
[Index]   Value      Size      Type  Bind  Other Shndx   Name
...............
[7]     |0x00000000|0x00000004|OBJT |WEAK |0x0  |3      |bar
[8]     |0x00000000|0x00000028|FUNC |WEAK |0x0  |2      |foo
[9]     |0x00000000|0x00000028|FUNC |GLOB |0x0  |2      |_foo

ウィークの別名 foo に、大域シンボル _foo と同じ属性が割り当てられていることに注意してください。この関係は、リンカーによって保持され、その結果、シンボルには出力イメージ内の同じ値が割り当てられます。シンボル解決においては、ウィーク定義シンボルは、同じ名前の大域定義によって自動的に上書きされます。

単純なシンボル解決のこの他の形式は、再配置可能オブジェクトと共有オブジェクト間、または複数の共有オブジェクト間に発生し、割り込み (interposition) と呼ばれます。このような場合、シンボルが複数回定義されている場合、リンカーにより、再配置可能オブジェクト、または複数の共有オブジェクト間の最初の定義が自動的に採用されます。再配置可能オブジェクトの定義、または最初の共有オブジェクトの定義は、他のすべての定義上に割り込みを行うといわれます。この割り込みを使用すると、1 つの共有オブジェクト、動的実行可能プログラム、または他の共有オブジェクトによって提供された機能を上書きできます。

ウィークシンボルと割り込みを組み合わせることにより、有用なプログラミングテクニックを使用できます。たとえば、標準 C ライブラリは、再定義可能ないくつかのサービスを提供していますが、ANSI C は、システム上になければならない一連の標準サービスを定義し、厳密に適合するプログラム内に置き換えることはできません。

たとえば、関数 fread(3C) は、ANSI C ライブラリの関数ですが、関数 read(2) は、ANSI C ライブラリの関数ではありません。適合する ANSI C プログラムは、read(2) を再定義でき、予測できる方法で fread(3C) を使用できなければなりません。

ここでの問題は、read(2) が、標準 C ライブラリ内に fread(3C) を実装する基盤になることです。このため、read(2) を再定義するプログラムは、fread(3C) の実装を混乱させる可能性があります。この混乱を避けるために、ANSI C は、実装には、そこに予約されていない名前は使用できないように定めています。以下に示す #pragma 指示語を使用することにより、この予約名を定義でき、この予約名から、関数 read(2) の別名が生成されます。


#pragma weak read = _read

こうすることにより、ユーザーは _read() 関数を使用している fread(3C) の実装を危険にさらすことなく、自分専用の read() 関数を自由に定義できます。

このリンカーでは、標準 C ライブラリの共有オブジェクトまたはアーカイブバージョンのどちらかにリンクしている場合でも、read() を再定義できます。前者の場合には、割り込みによって方法が決められます。 後者の場合には、read(2) のC ライブラリの定義をウィークにすることにより、自動的に上書き可能になります。

リンカーの -m オプションを使用すれば、割り込みされるすべてのシンボル参照のリストを、セクションの読み込みアドレス情報とともに標準出力に書き込むことができます。

複雑な解決

複雑な解決は、同じ名前を持つ 2 つのシンボルが、異なる属性とともに検出された場合に発生します。この場合、リンカーは最も適切なシンボルを選択し、そのシンボル、対立する属性、およびそのシンボル定義を取り出したファイルの ID を示す警告メッセージを生成します。次の例では、データ項目の配列の定義が指定された 2 つのファイルで、サイズの必要条件が異なっています。


$ cat foo.c
int array[1];

$ cat bar.c
int array[2] = { 1, 2 };

$ cc -dn -r -o temp.o foo.c bar.c
ld: warning: symbol `array' has differing sizes:
        (file foo.o value=0x4; file bar.o value=0x8);
        bar.o definition taken

シンボルの整列必要条件が異なる場合には、同様の診断プログラムが作成されます。この 2 つのケースの場合、リンカーの -t オプションを使用すると、診断プログラムを抑制できます。

この他の属性形式の違いに、シンボルのタイプがあります。次の例では、シンボル bar() は、データ項目と関数の両方として定義されています。


$ cat foo.c
bar()
{
        return (0);
}
$ cc -o libfoo.so -G -K pic foo.c
$ cat main.c
int     bar = 1;

main()
{
        return (bar);
}
$ cc -o main main.c -L. -lfoo
ld: warning: symbol `bar' has differing types:
        (file main.o type=OBJT; file ./libfoo.so type=FUNC);
        main.o definition taken

注 –

この文脈では、シンボルのタイプは ELF で使用されるタイプです。このシンボルタイプは、単純な形式であることを除けば、プログラミング言語で使用されるデータ型には関連していません。


前述の例のような場合で、再配置可能オブジェクトと共有オブジェクト間で解決が発生したときには、再配置可能オブジェクトの定義がとられます。また、2 つの共有オブジェクト間で解決が発生した場合には、最初の共有オブジェクトの定義がとられます。このような解決が異なる結合間 (ウィークまたは大域) で発生すると、警告メッセージも同時に作成されます。

リンカーの -t オプションを使用しても、シンボルタイプ間の不一致は抑制できません。

重大な解決

解決できないシンボルの衝突があると、重大なエラー状態になります。この場合、シンボルを入手したファイルの名前と同時に、そのシンボル名を指摘した適切なエラーメッセージが表示されて、出力ファイルは生成されません。この重大なエラー状態によってリンカーは停止しますが、すべての入力ファイルの処理が、まず最初に完了します。この要領で、重大な解決エラーをすべて識別できます。

最も一般的な、重大エラー状態は、2 つの再配置可能オブジェクトが両方とも同じ名前のシンボルを定義していて、どちらのシンボルもウィーク定義ではない場合に発生します。


$ cat foo.c
int bar = 1;

$ cat bar.c
bar()
{ 
        return (0);
}

$ cc -dn -r -o temp.o foo.c bar.c
ld: fatal: symbol `bar' is multiply-defined:
        (file foo.o and file bar.o);
ld: fatal: File processing errors. No output written to int.o

foo.cbar.c には、シンボル bar に対する重複する定義があります。リンカーは、どちらを優先すべきか判別できないため、通常はエラーメッセージを出力して終了します。リンカーの -z muldefs オプションを使用すると、このエラー状態を防ぐことができ、リンカーが、最初のシンボル定義を優先するように設定できます。

未定義シンボル

すべての入力ファイルを読み取り、シンボル解決がすべて完了すると、リンカーは、シンボル定義に結合されていないシンボル参照の内部シンボルテーブルを検索します。これらのシンボル参照は、未定義シンボルと呼ばれます。これらの未定義シンボルがリンク編集処理に及ぼす影響は、生成される出力ファイルのタイプや、場合によってはシンボルのタイプによって異なります。

実行可能ファイルの作成

リンカーが実行可能ファイルを作成しているときは、リンカーのデフォルトの動作は、シンボルを定義されないままにする必要がある適切なエラーメッセージを表示してリンク編集を終了させることです。次のように、再配置可能オブジェクト内のシンボル参照が、シンボル定義と絶対に一致しない場合に、シンボルは定義されないままの状態になります。


$ cat main.c
extern int foo();

main()
{
        return (foo());
}
$ cc -o prog main.c
Undefined           first referenced
 symbol                 in file
foo                     main.o
ld: fatal: Symbol referencing errors. No output written to prog

これと同様の方法で、共有オブジェクトが動的実行可能プログラムを作成するために使用されているときに、共有オブジェクト内のシンボル参照が、シンボル定義と絶対に一致しない場合は、このシンボル参照も未定義シンボルになります。


$ cat foo.c
extern int bar;
foo()
{
        return (bar);
}

$ cc -o libfoo.so -G -K pic foo.c
$ cc -o prog main.c -L. -lfoo
Undefined           first referenced
 symbol                 in file
bar                     ./libfoo.so
ld: fatal: Symbol referencing errors. No output written to prog

上記の例のような場合に、未定義シンボルを許可するには、リンカーの -z nodefs オプションを使用することにより、重大なエラー状態を防ぐことができます。


注 –

-z nodefs オプションを使用する場合は、注意が必要です。処理の実行中に使用できないシンボル参照が要求されると、重大な実行時再配置エラーが発生します。このエラーは、アプリケーションを最初に実行してテストする際に検出できる可能性があります。しかし、実行パスがより複雑であるとエラー状態の検出に時間がかかり、時間とコストが浪費される場合があります。


シンボルは、再配置可能オブジェクト内のシンボル参照が、暗黙の内に定義された共有オブジェクト内のシンボル定義に結合されている場合にも、未定義シンボルのままになる場合があります。たとえば、上記の例で使用したファイル main.c および foo.c に以下のように続く場合です。


$ cat bar.c
int bar = 1;

$ cc -o libbar.so -R. -G -K pic bar.c -L. -lfoo
$ ldd libbar.so
        libfoo.so =>     ./libfoo.so

$ cc -o prog main.c -L. -lbar
Undefined           first referenced
 symbol                 in file
foo                     main.o  (symbol belongs to implicit \
                        dependency ./libfoo.so)
ld: fatal: Symbol referencing errors. No output written to prog

prog は、libbar.so への「明示的な」参照を使用して構築されます。また、libbar.so には libfoo.so への依存性があるため、prog から libfoo.so への暗黙的な参照が確立します。

main.c は、libfoo.so によって作成されたインタフェースへの特定の参照を実行するため、prog は、実際に libfoo.so に依存性を持つことになります。ただし、生成される出力ファイル内に記録されるのは、明示的な共有オブジェクトの依存関係だけです。そのため、libbar.so の新しいバージョンが開発され、libfoo.so への依存性がなくなった場合、prog は実行に失敗します。

この理由から、このタイプのバインディングは重大であると考えられ、暗黙的な参照は、prog のリンク編集中にライブラリを直接参照することにより、明示的に実行される必要があります。この例で示した重大なエラーメッセージ内に必要な参照のヒントがあります。

共有オブジェクト出力ファイルの生成

リンカーが共有オブジェクト出力ファイルを生成する場合、未定義シンボルをリンク編集の後も残すことができます。このデフォルトの動作により、共有オブジェクトを動的実行可能ファイルの作成に使用する場合、共有オブジェクトはシンボルを再配置可能オブジェクトまたは他の共有オブジェクトのどちらからでもインポートできます。

リンカーの -z defs オプションを使用すると、未定義シンボルが残っていた場合に、強制的に重大エラーにすることができます。共有オブジェクトを作成するときには、このオプションの使用をお勧めします。アプリケーションのシンボルを参照する共有オブジェクトでは、-z defs オプションを使用すれば、extern mapfile 指示文を使ってアプリケーションシンボルを定義できます。これについては、追加シンボルの定義を参照してください。

自己完結型の共有オブジェクトは、外部シンボルへのすべての参照は指定された依存関係によって満たされ、最大の柔軟性が提供されます。この共有オブジェクトは、共有オブジェクトの必要条件を満たす依存関係を判別し確立する手間をユーザーにかけることなく、多数のユーザーによって使用されます。

ウィークシンボル

生成中の出力ファイルタイプがどのようなタイプであっても、リンク編集中に結合されないウィークシンボル参照により、重大なエラー状態が発生します。

静的実行可能プログラムを生成中の場合は、シンボルは絶対シンボルに変換され、ゼロの値が割り当てられます。

動的実行可能ファイルまたは共有オブジェクトの作成中の場合は、シンボルは定義されていないウィーク参照として残され、値には 0 が割り当てられます。プロセスの実行中に、実行時リンカーがこのシンボルを検索します。一致が検出されない場合、実行時リンカーは重大な実行時再配置エラーを生成する代わりに、その参照をゼロのアドレスに結合します。

従来は、これらの定義されていないウィーク参照シンボルは、機能の存在をテストするためのメカニズムとして使用されていました。たとえば、次の C コードフラグは、共有オブジェクト libfoo.so.1 内で次のように使用されていました。


#pragma weak    foo

extern  void    foo(char *);

void bar(char * path)
{
        void (* fptr)(char *);

        if ((fptr = foo) != 0)
                (* fptr)(path);
}

アプリケーションが参照 libfoo.so.1 で構築されると、シンボル foo の定義が検出されたかどうかに関係なく、リンク編集は、正常に完了します。アプリケーションの実行中に、機能アドレスがゼロ以外をテストすると、その機能が呼び出されます。ただし、シンボル定義が検出されない場合には、機能アドレスはゼロをテストするため、その機能は呼び出されません。

コンパイルシステムは、定義されないセマンティクスを保持しながら、このアドレスの比較テクニックを参照します。その結果、テストステートメントは最適化処理によって削除されます。さらに、実行時シンボルの結合メカニズムでは、このテクニックの使用にこれ以外の制限も加え、これにより、すべての動的オブジェクトが整合性のあるモデルを使用できる状態ではなくなります。


注 –

未定義のウィーク参照をこのように使用することは避けてください。RTLD_DEFAULT フラグを指定した dlsym(3DL) を使用してシンボルの存在テストを行うことをお勧めします。 機能のテストを参照してください。


出力ファイル内の一時的シンボル順序

入力ファイルの追加は、通常、その追加の順に出力ファイルに表示されます。ただし、一時的シンボルとそれに関連する記憶領域を処理するときに、例外が発生します。一時的シンボルは、その解決が完了するまで完全に定義されません。再配置可能オブジェクトからの定義シンボルに遭遇すると、解決が実行されます。すると、表示される順序は、定義を調べるために実行された結果になります。

シンボルグループの順序を制御する必要がある場合には、一時的定義は、ゼロで初期化されたデータ項目に再定義する必要があります。たとえば、次のような一時的定義をすると、出力ファイル内のデータ項目が、ソースファイル foo.c に記述された元の順序と比較されて再配列されます。


$ cat foo.c
char A_array[0x10];
char B_array[0x20];
char C_array[0x30];

$ cc -o prog main.c foo.c
$ nm -vx prog | grep array
[32]    |0x00020754|0x00000010|OBJT |GLOB |0x0  |15  |A_array
[34]    |0x00020764|0x00000030|OBJT |GLOB |0x0  |15  |C_array
[42]    |0x00020794|0x00000020|OBJT |GLOB |0x0  |15  |B_array

これらのシンボルを、初期化されたデータ項目として定義することにより、入力ファイル内のこれに関連したシンボルの配列が、出力ファイル内にも持ち越されます。


$ cat foo.c
char A_array[0x10] = { 0 };
char B_array[0x20] = { 0 };
char C_array[0x30] = { 0 };

$ cc -o prog main.c foo.c
$ nm -vx prog | grep array
[32]    |0x000206bc|0x00000010|OBJT |GLOB |0x0  |12  |A_array
[42]    |0x000206cc|0x00000020|OBJT |GLOB |0x0  |12  |B_array
[34]    |0x000206ec|0x00000030|OBJT |GLOB |0x0  |12  |C_array

追加シンボルの定義

シンボルを入力ファイルから提供することに加えて、ユーザーは、リンク編集に、追加のシンボル参照または定義を指定できます。最も簡単な形式で、シンボル参照は、リンカーの -u オプションを使用して作成できます。より柔軟性の高いものは、リンカーの -M オプションと、それに関連した、シンボル参照と種々のシンボル定義を定義できる mapfile を使用して作成できます。

-u オプションを指定すると、リンク編集コマンド行からシンボル参照を作成するためのメカニズムが使用できます。このオプションは、リンク編集をすべてアーカイブから実行する場合に使用でき、また、複数のアーカイブから抽出するオブジェクトの選択における柔軟性を向上させることができます。アーカイブの抽出については、アーカイブ処理の項を参照してください。

たとえば、動的実行可能プログラムを、シンボル foobar への参照を実行する再配置可能オブジェクト main.o から生成するとします。この場合、lib1.a 内に組み込まれた再配置可能オブジェクト foo.o からシンボル定義 foo を入手し、さらに lib2.a 内に組み込まれた再配置可能オブジェクト bar.o からシンボル定義 bar を入手します。

ただし、アーカイブ lib1.aにも、シンボル bar を定義する再配置可能オブジェクトが組み込まれています。この再配置可能オブジェクトは、lib2.a に提供されたものとは機能的に異なると想定します。必要なアーカイブ抽出を指定する場合は、次のようなリンク編集を使用できます。


$ cc -o prog -L. -u foo -l1 main.o -l2

-u オプションは、シンボル foo への参照を生成します。この参照によって、再配置可能オブジェクト foo.o がアーカイブ lib1.a から抽出されます。シンボル bar への最初の参照は lib1.a が処理されてから生じる main.o 内で実行されます。このため、再配置可能オブジェクト bar.o はアーカイブ lib2.a から入手されます。


注 –

この単純な例では、lib1.a からの 再配置可能オブジェクト foo.o は、シンボル bar の直接的または間接的な参照は行いません。この参照を行なった場合、再配置可能オブジェクト bar.o は、その処理中に、 lib1.a から抽出されます。アーカイブを処理するリンカーの多重パスについては、アーカイブ処理 を参照してください。


より広範囲なシンボル定義のセットは、リンカーの -M オプションと関連する mapfile を使用して入手できます。これらの mapfile エントリの構文は次のとおりです。


[ name ] {
      scope:
            symbol [ = [ type ] [ value ] [ size ] [ attribute ] ];
} [ dependency ];
name

このシンボル定義のセットのラベルは、もしあれば、イメージ内のバージョン定義を識別できます。第 5 章「アプリケーションバイナリインタフェースとバージョン管理」を参照してください。

scope

生成される出力ファイル内のシンボルのバインディングの可視性を示しています。mapfile で定義されたすべてのシンボルは、リンク編集プロセス中に、スコープ内で global (大域) として処理されます。つまり、これらのシンボルは、入力ファイルのいずれかから入手された、同じ名前の他のシンボルに対して解決されます。以下の定義と別名は、作成されるオブジェクト内におけるシンボルの可視性を定義します。

default / global

このスコープのシンボルは、ほかの外部オブジェクトから見えます。このタイプのシンボルに対するオブジェクト内からの参照は実行時に結合されるため、介入が可能となります。

protected / symbolic

このスコープのシンボルは、ほかの外部オブジェクトから見えます。これらのシンボルに対するオブジェクト内からの参照はリンク編集時に結合されるため、実行時の介入は防止されます。このスコープ定義には、シンボルに STV_PROTECTED 可視性が指定された場合と同じ効果があります。表 7–24を参照してください。

hidden / local

このスコープのシンボルは、ローカル結合されるシンボルに縮小されます。このスコープのシンボルは、ほかの外部オブジェクトから見えません。このスコープ定義には、シンボルに STV_HIDDEN 可視性が指定された場合と同じ効果があります。表 7–24を参照してください。

eliminate

このスコープのシンボルは hidden です。これらのシンボルテーブルのエントリは削除されます。

symbol

要求されたシンボルの名前です。この名前の後にシンボル属性 type valuesize、または extern のいずれかがついていない場合、シンボル参照が作成されます。この参照は、この項の最初に説明した -u オプションを使用して生成する参照とまったく同じものです。このシンボル名にシンボル属性が付いている場合には、シンボル定義は、関連する属性を使用して生成されます。

local スコープ内では、このシンボル名は、特別な「auto-reduction」(自動縮小) 指示語「*」として定義できます。この指示語を使用すると、すべての大域シンボル (mapfile 内に global と明示的に定義されていないもの) は、生成される動的オブジェクトファイル内でローカル結合されます。

type

シンボルのタイプ属性を示します。この属性は、datafunction、または COMMON のいずれかです。最初の 2 つのタイプ属性の結果は、絶対的なシンボル定義になります 。シンボルテーブルセクションを参照してください。 後者のタイプ属性の結果は、一時的シンボル定義になります。

value

シンボルの値属性を示し、Vnumber の書式です。

size

シンボルのサイズ属性を示し、Vnumber の書式です。

attribute

このキーワードは、シンボルに以下の追加属性を提供します。

EXTERN

シンボルが、作成されるオブジェクトの外部で定義されていることを示します。この属性は、 個々の直接参照または間接参照を確立するために、DIRECT 属性または NODIRECT 属性と併用できます。また、このオプションを使用して、-z defs オプションで示される未定義シンボルを抑制することもできます。

DIRECT

このシンボルを直接結合する必要があることを示します。この属性は、外部シンボルへの結合を制御する EXTERN 属性と併用できます。直接結合を参照してください。

NODIRECT

このシンボルを直接結合してはならないことを示します。この状態は、作成されるオブジェクト内からの参照と外部参照に適用されます。この属性は、外部シンボルへの結合を制御する EXTERN 属性と併用できます。直接結合を参照してください。

dependency

この定義が継承するバージョン定義を示します。第 5 章「アプリケーションバイナリインタフェースとバージョン管理」を参照してください。

バージョン定義または自動縮小のいずれかの指示語が指定されている場合、バージョン情報が作成されるイメージ内に記録されます。このイメージが実行可能プログラムまたは共有オブジェクトである場合には、シンボル縮小も適用されます。

作成されるイメージが再配置可能オブジェクトである場合は、デフォルトにより、シンボル縮小は適用されません。この場合、シンボル縮小はバージョン情報の一部として記録されます。これらの縮小は、再配置可能オブジェクトが最終的に実行可能ファイルまたは共有オブジェクトの生成に使用されるときに適用されます。リンカーの -B reduce オプションを使用すると、再配置可能オブジェクトを生成するときに、強制的にシンボル縮小を実行できます。

バージョン情報の詳細については、第 5 章「アプリケーションバイナリインタフェースとバージョン管理」に記載してあります。


注 –

インタフェース定義を確実に安定させるためには、シンボル名の定義に対しワイルドカードによる拡張を行わないようにします。


この項では、この mapfile 構文を使用した例をいくつか示します。

次の例では、3 つのシンボル参照を定義する方法を示します。これらの参照を使用して、アーカイブの構成要素を抽出します。このアーカイブ抽出は、複数の -u オプションをリンク編集に指定することにより実現できますが、この例では、最終的なシンボルの範囲を、ローカルに縮小する方法も示しています。


$ cat foo.c
foo()
{
        (void) printf("foo: called from lib.a\n");
}
$ cat bar.c
bar()
{
        (void) printf("bar: called from lib.a\n");
}
$ cat main.c
extern  void    foo(), bar();

main()
{
        foo();
        bar();
}
$ ar -rc lib.a foo.o bar.o main.o
$ cat mapfile
{
        local:
                foo;
                bar;
        global:
                main;
};
$ cc -o prog -M mapfile lib.a
$ prog
foo: called from lib.a
bar: called from lib.a
$ nm -x prog | egrep "main$|foo$|bar$"
[28]    |0x00010604|0x00000024|FUNC |LOCL |0x0  |7      |foo
[30]    |0x00010628|0x00000024|FUNC |LOCL |0x0  |7      |bar
[49]    |0x0001064c|0x00000024|FUNC |GLOB |0x0  |7      |main

大域からローカルへのシンボル範囲の縮小の重要性については、シンボル範囲の縮小で説明しています。

次の例では、2 つの絶対シンボル定義を定義する方法を示します。そして、これらの定義を使用して、入力ファイル main.c からの参照を解決します。


$ cat main.c
extern  int     foo();
extern  int     bar;

main()
{
        (void) printf("&foo = %x\n", &foo);
        (void) printf("&bar = %x\n", &bar);
}
$ cat mapfile
{
        global:
                foo = FUNCTION V0x400;
                bar = DATA V0x800;
};
$ cc -o prog -M mapfile main.c
$ prog
&foo = 400 &bar = 800
$ nm -x prog | egrep "foo$|bar$"
[37]    |0x00000800|0x00000000|OBJT |GLOB |0x0  |ABS    |bar
[42]    |0x00000400|0x00000000|FUNC |GLOB |0x0  |ABS    |foo

入力ファイルから入手される場合、関数のシンボル定義またはデータ項目は、通常、データ記憶域の要素に関連しています。mapfile 定義は、このデータ記憶域を構成するためには不十分であるため、これらのシンボルは、絶対値として残しておく必要があります。

ただし、mapfile は、COMMON または一時的シンボルを定義する場合にも使用できます。他のタイプのシンボル定義とは違って、一時的シンボルは、ファイル内の記憶域を占有しませんが、実行時に割り当てる記憶域の定義は行います。そのため、このタイプのシンボル定義は、作成される出力ファイルの記憶域割り当ての一因となります。

一時的シンボルの特徴は、他のシンボルタイプとは異なり、その値の属性によって、その配列条件が示される点です。そのため、リンク編集の入力ファイルから入手される一時的定義の再配列に mapfile 定義を使用できます。

次の例では、2 つの一時的シンボルの定義を示しています。シンボル foo は、新しい記憶領域を定義しているのに対し、シンボル bar は、実際に、ファイル main.c 内の同じ一時的定義の配列を変更するために使用されます。


$ cat main.c
extern  int     foo;
int             bar[0x10];

main()
{
        (void) printf("&foo = %x\n", &foo);
        (void) printf("&bar = %x\n", &bar);
}
$ cat mapfile
{
        global:
                foo = COMMON V0x4 S0x200;
                bar = COMMON V0x100 S0x40;
};
$ cc -o prog -M mapfile main.c
ld: warning: symbol `bar' has differing alignments:
        (file mapfile value=0x100; file main.o value=0x4);
        largest value applied
$ prog
&foo = 20940
&bar = 20900
$ nm -x prog | egrep "foo$|bar$"
[37]    |0x00020900|0x00000040|OBJT |GLOB |0x0  |16     |bar
[42]    |0x00020940|0x00000200|OBJT |GLOB |0x0  |16     |foo

注 –

このシンボル解決の診断は、リンカーの -t オプションを使用すると表示されません。


シンボル範囲の縮小

mapfile 内のローカル範囲を持つようにシンボル定義を定義するとシンボルの最終的な結合を縮小できます。このメカニズムは、入力の一部として生成されたファイルを使用する、将来のリンク編集に対するシンボルの可視性を削減するという重要な役割を果たします。実際、このメカニズムは、ファイルのインタフェースの厳密な定義をするために提供されているため、他のユーザーに対して、機能の使用を制限できます。

たとえば、簡単な共有オブジェクトを、ファイル foo.cbar.c から生成するとします。ファイル foo.c には、他のユーザーも使用できるように設定するサービスを提供する大域シンボル foo が組み込まれています。ファイル bar.c には、共有オブジェクトの根底となるインプリメンテーションを提供するシンボル barstr が組み込まれています。簡単に作成された共有オブジェクトは、通常、これらの 3 つのシンボルすべてに、次のように大域範囲が指定されています。


$ cat foo.c
extern  const char *    bar();

const char * foo()
{
        return (bar());
}
$ cat bar.c
const char * str = "returned from bar.c";

const char * bar()
{
        return (str);
}
$ cc -o lib.so.1 -G foo.c bar.c
$ nm -x lib.so.1 | egrep "foo$|bar$|str$"
[29]    |0x000104d0|0x00000004|OBJT |GLOB |0x0  |12     |str
[32]    |0x00000418|0x00000028|FUNC |GLOB |0x0  |6      |bar
[33]    |0x000003f0|0x00000028|FUNC |GLOB |0x0  |6      |foo

このようにすると、この共有オブジェクトが提供する機能を、他のアプリケーションのリンク編集の一部として使用できます。シンボル foo への参照は、共有オブジェクトによって提供されたインプリメンテーションに結合されます。

大域結合により、シンボル barstr への直接参照も可能です。ただし、これは危険な結果を招く場合があります。関数 foo の基礎となるインプリメンテーションは、後から変更することがあるためです。それが原因で知らないうちに、bar または str に結合された既存のアプリケーションが失敗または誤作動を起こす可能性があります。

また、シンボル barstr を大域結合すると、同じ名前のシンボルによって割り込まれる可能性があります。共有オブジェクト内へのシンボルの割り込みについては、単純な解決 の項で説明しています。この割り込みは、意図的に行うことができ、これを使用することにより、共有オブジェクトが提供する目的の機能を取り囲むことができます。また反対に、この割り込みは、同じ共通のシンボル名をアプリケーションと共有オブジェクトの両方に使用した結果として、知らないうちに実行される場合もあります。

共有オブジェクトを開発する場合は、シンボル barstr の範囲をローカル結合に縮小して、このような事態から保護することができます。次の例のシンボル barstr は、共有オブジェクトのインタフェースの一部としては使用できません。そのため、これらのシンボルは、外部のオブジェクトによって参照されることができないか、割り込みはできません。ユーザーは、インタフェースをこの共有オブジェクト用に効果的に定義できます。インプリメンテーションの基礎となる詳細を隠している間は、このインタフェースを管理できます。


$ cat mapfile
{
        local:
                bar;
                str;
};
$ cc -o lib.so.1 -M mapfile -G foo.c bar.c
$ nm -x lib.so.1 | egrep "foo$|bar$|str$"
[27]    |0x000003dc|0x00000028|FUNC |LOCL |0x0  |6      |bar
[28]    |0x00010494|0x00000004|OBJT |LOCL |0x0  |12     |str
[33]    |0x000003b4|0x00000028|FUNC |GLOB |0x0  |6      |foo

このようなシンボル範囲の縮小には、この他にもパフォーマンスにおける利点があります。実行時に必要だったシンボル barstr に対するシンボルの再配置は、現在は、関連する再配置に縮小されます。これにより、実行時の、共有オブジェクトの初期設定と処理のオーバーヘッドが削減されます。シンボル再配置のオーバーヘッドの詳細は、再配置を実行する場合 を参照してください。

リンク編集間で処理されるシンボル数が多くなると、mapfile 内で各ローカル範囲への縮小を定義する能力の維持が困難になります。その代わりとなる、より柔軟性のあるメカニズムを使用すると、共有オブジェクトインタフェースを、保持する必要がある大域シンボルとして定義でき、他のシンボルはすべてローカル結合に縮小するようにリンカーに指示できます。このメカニズムは、特別な 自動縮小指示語の「*」を使用して実行します。たとえば、前の mapfile 定義を書き換えて、foo を、生成される出力ファイル内で必要な大域シンボルとしてのみ定義します。


$ cat mapfile
lib.so.1.1
{
        global:
                foo;
        local:
                *;
};
$ cc -o lib.so.1 -M mapfile -G foo.c bar.c
$ nm -x lib.so.1 | egrep "foo$|bar$|str$"
[30]    |0x00000370|0x00000028|FUNC |LOCL |0x0  |6      |bar
[31]    |0x00010428|0x00000004|OBJT |LOCL |0x0  |12     |str
[35]    |0x00000348|0x00000028|FUNC |GLOB |0x0  |6      |foo

この例では、バージョン名 lib.so.1.1mapfile 指示語の一部として定義しています。このバージョン名により、ファイルのシンボルインタフェースを定義する、内部バージョン定義が確立されます。バージョン定義はできるだけ作成してください。バージョン定義によって、ファイルの展開全体を通して使用できる、内部バージョンメカニズムの基礎が形成されます。第 5 章「アプリケーションバイナリインタフェースとバージョン管理」を参照してください。


注 –

バージョン名が指定されていないと、出力ファイル名がバージョン定義のラベル付けに使用されます。出力ファイル内に作成されたバージョン情報は、リンカーの -z noversion オプションを使用して表示しないようにできます。


バージョン名が指定されている場合は必ず、すべての大域シンボルをバージョン定義に割り当てる必要があります。バージョン定義に割り当てられていない大域シンボルが残っていると、リンカーにより重大なエラー状態が発生します。


$ cat mapfile
lib.so.1.1 {
        global:
                foo;
};
$ cc -o lib.so.1 -M mapfile -G foo.c bar.c
Undefined           first referenced
 symbol                 in file
str                     bar.o  (symbol has no version assigned)
bar                     bar.o  (symbol has no version assigned)
ld: fatal: Symbol referencing errors. No output written to lib.so.1

-B local オプションを使用して、コマンド行から自動縮小指示語「*」を表明することができます。 したがって、前述のコンパイル例を正常終了させるには、次のように指定します。


$ cc -o lib.so.1 -M mapfile -B local -G foo.c bar.c

実行可能プログラムまたは共有オブジェクトを作成するときに、シンボルの縮小処理が行われると、該当するシンボルが縮小されると同時に、出力イメージ内にバージョン定義が記録されます。再配置可能オブジェクトの生成時にバージョン定義は作成されますが、シンボルの縮小処理は行われません。その結果、シンボル縮小のシンボルエントリは、大域のまま残されます。たとえば、自動縮小指示語が指定された、前の mapfile と関連する再配置可能オブジェクトを使用して、シンボル縮小が表示されていない中間再配置可能オブジェクトが作成されます。


$ cat mapfile
lib.so.1.1 {
        global:
                foo;
        local:
                *;
};
$ ld -o lib.o -M mapfile -r foo.o bar.o
$ nm -x lib.o | egrep "foo$|bar$|str$"
[17]    |0x00000000|0x00000004|OBJT |GLOB |0x0  |3      |str
[19]    |0x00000028|0x00000028|FUNC |GLOB |0x0  |1      |bar
[20]    |0x00000000|0x00000028|FUNC |GLOB |0x0  |1      |foo

このイメージ内に作成されたバージョン定義は、シンボル縮小が要求されたという事実を記録します。再配置可能オブジェクトが、最終的に、実行可能ファイルまたは共有オブジェクトの生成に使用されるときに、シンボル縮小が実行されます。すなわち、リンカーは、mapfile からデータを処理するのと同じ方法で、再配置可能オブジェクト内に組み込まれたシンボル縮小を読み取り、解釈します。

そのため、上記の例で作成された中間再配置可能オブジェクトは、ここで、共有オブジェクトの生成に使用されます。


$ ld -o lib.so.1 -G lib.o
$ nm -x lib.so.1 | egrep "foo$|bar$|str$"
[22]    |0x000104a4|0x00000004|OBJT |LOCL |0x0  |14     |str
[24]    |0x000003dc|0x00000028|FUNC |LOCL |0x0  |8      |bar
[36]    |0x000003b4|0x00000028|FUNC |GLOB |0x0  |8      |foo

シンボル縮小は、通常、実行可能ファイルまたは共有オブジェクトが作成されたときに行う必要があります。ただし、再配置可能オブジェクトが作成されたときは、リンカーの -B reduce オプションを使用して強制的に実行されます。


$ ld -o lib.o -M mapfile -B reduce -r foo.o bar.o
$ nm -x lib.o | egrep "foo$|bar$|str$"
[15]    |0x00000000|0x00000004|OBJT |LOCL |0x0  |3      |str
[16]    |0x00000028|0x00000028|FUNC |LOCL |0x0  |1      |bar
[20]    |0x00000000|0x00000028|FUNC |GLOB |0x0  |1      |foo

シンボル削除

この操作は、シンボル縮小の拡張で、オブジェクトのシンボルテーブルからシンボルエントリを削除します。ローカルシンボルは、オブジェクトの .symtab シンボルテーブルだけで管理されます。このテーブルは、リンカーの -s オプションまたは strip(1) を使用して、オブジェクトからすべて削除できます。しかし、.symtab シンボルテーブルは削除しないで、特定のローカルシンボルだけを削除したいこともあります。

シンボル削除は、mapfile の指示語 eliminate を使用して実行できます。 local 指示語と同様に、シンボルを個別に指定でき、また、特別な自動削除指示語「*」として指定できます。次の例では、前述のシンボル縮小の例で使用したシンボル bar を削除しています。


$ cat mapfile
lib.so.1.1
{
        global:
                foo;
        local:
                str;
        eliminate:
                *;
};
$ cc -o lib.so.1 -M mapfile -G foo.c bar.c
$ nm -x lib.so.1 | egrep "foo$|bar$|str$"
[31]    |0x00010428|0x00000004|OBJT |LOCL |0x0  |12     |str
[35]    |0x00000348|0x00000028|FUNC |GLOB |0x0  |6      |foo

-B eliminate オプションを使用して、コマンド行から自動削除指示語「*」を指定することもできます。

外部結合

作成するオブジェクトのシンボル参照が共有オブジェクト内の定義によって解決されると、そのシンボルは未定義のまま残ります。シンボルに対応する再配置情報が実行時の検索で使用されます。定義を提供する共有オブジェクトは、通常、1 つの依存条件になります。

実行時リンカーは、実行時にデフォルト検索モデルを使ってこの定義を見つけます。一般にリンカーはオブジェクトを 1 つずつ検索しますが、その際、動的実行可能プログラムから始め、オブジェクトが読み込まれた順に各依存関係を処理します 。

オブジェクトは、直接結合を使用するように作成することもできます。この方法では、シンボル参照と、シンボル定義を提供するオブジェクトとの関係は、作成されるオブジェクト内に維持されます。この情報を使えば、実行時リンカーは、参照先とシンボルを定義するオブジェクトを直接結合し、デフォルトのシンボル検索モデルをバイパスできます。直接結合を参照してください。

文字列テーブルの圧縮

リンカーは、重複したエントリと末尾部分文字列を削除することによって、文字列テーブルを圧縮します。この圧縮により、どのような文字列テーブルでもサイズが相当小さくなります。.dynstr テーブルを圧縮すると、テキストセグメントが小さくなるため、実行時のページング作業が減ります。このような利点があるため、文字列テーブルの圧縮はデフォルトで有効に設定されています。

非常に多くのシンボルを提供するオブジェクトをリンクする場合には、文字列テーブルの圧縮のためにリンク編集時間が延びる可能性があります。開発時にこの負担を避けるには、リンカーの -z nocompstrtab オプションを使用してください。リンク編集時に行われる文字列テーブルの圧縮は、リンカーのデバッグトークン -D strtab,detail を使用して表示できます。

出力ファイルの生成

入力ファイルの処理とシンボル解決がすべて重大なエラーが発生することもなく完了すると、リンカーは出力ファイルの生成を開始します。リンカーは、出力ファイルを完成させるために生成する必要がある追加セクションを確立します。これらのセクションには、入力ファイルからのローカルシンボル定義が組み込まれたシンボルテーブルとともに、リンカーの内部シンボルテーブルから収集された大域およびウィークシンボル情報が組み込まれます。

また、実行時リンカーが必要とする、出力の再配置および動的情報セクションも組み込まれます。出力セクション情報が確立されると、出力ファイルの合計サイズが算出され、それに従って出力ファイルイメージが作成されます。

動的実行可能プログラムまたは共有オブジェクトを作成するときに、通常、2 つのシンボルテーブルが生成されます。.dynsym とその関連文字列テーブル .dynstr には、レジスタ (これらがローカルであっても)、大域シンボル、ウィークシンボル、およびセクションシンボルが組み込まれます。これらのセクションは、実行時にプロセスイメージの一部として対応付けされる (mmap(2) のマニュアルページを参照) text セグメントの一部になります。これにより、実行時リンカーは、これらのセクションを読み取り、必要な再配置を実行できます。

.symtab とその関連文字列テーブル .strtab には、入力ファイル処理から収集されたすべてのシンボルが含まれています。これらのセクションは、プロセスイメージの一部として対応付けられません。これらのセクションは、リンカーの -s オプションを使用して、または、リンク編集後に strip(1) を使用して、イメージから取り除くことさえ可能です。

予約シンボルは、シンボルテーブルの生成中に作成されます。予約シンボルは、リンクプロセスに対する特別な意味を持ち、ユーザーのコードでは定義できません。

_etext

テキストセグメントの後の最初のロケーション

_edata

初期化されたデータの最初のロケーション

_end

すべてのデータの後の最初のロケーション

_DYNAMIC

動的情報セクション (.dynamic セクション) のアドレス

_END_

_end と同じです。 このシンボルは、_START_ とともに、ローカル範囲を持ち、オブジェクトのアドレス範囲を確立する手段を提供します。

_GLOBAL_OFFSET_TABLE_

リンカーが提供するアドレステーブル (.got セクション) への位置に依存しない参照。このテーブルは、-K pic オプションを指定してコンパイルしたオブジェクトで発生する、位置に依存しないデータ参照から構築されます。位置に依存しないコードを参照してください。

_PROCEDURE_LINKAGE_TABLE_

リンカーが提供するアドレステーブル (.plt セクション) への位置に依存しない参照。このテーブルは、-K pic オプションを指定してコンパイルしたオブジェクトで発生する、位置に依存しない関数参照から構築されます。位置に依存しないコードを参照。

_START_

テキストセグメント内の最初のロケーション。このシンボルは、_END_ とともに、ローカル範囲を持ち、オブジェクトのアドレス範囲を確立する手段を提供します。

リンカーは、実行可能ファイルを生成する場合、追加シンボルを検出して実行可能ファイルのエントリポイントを定義します。シンボルがリンカーの -e オプションを使用して指定された場合、そのシンボルが使用されます。それ以外の場合は、リンカーは予約シンボル名 _startmain を検出します。これらのシンボルが存在しない場合には、テキストセグメントの最初のアドレスが使用されます。

再配置処理

出力ファイルを作成すると、入力ファイルからのすべてのデータセクションは新しいイメージにコピーされます。入力ファイル内に指定された再配置は、出力イメージに適用されます。生成する必要がある追加の再配置情報も、新しいイメージに書き込まれます。

再配置処理には、通常、大きな問題はありませんが、特定のエラーメッセージを伴うエラー状態が発生することがあります。ここでは、2 つの状態について説明します。1 つは、位置に依存するコードによって発生するテキスト再配置です。この状態の詳細については、位置に依存しないコード を参照してください。 もう 1 つは、ディスプレイスメント再配置に関連して発生します。ディスプレイスメント再配置については、次の項で詳しく説明します。

ディスプレイスメント再配置

データ項目 (それ自体はコピー再配置で使用可能) にディスプレイスメント再配置が適用されていると、エラー状態が発生することがあります。コピー再配置の詳細については、コピー再配置を参照してください。

ディスプレイスメント再配置は、再配置されるオフセットと再配置される先のターゲットが両方とも同じ位置だけ離れている限り有効です。コピー再配置とは、共有オブジェクト内の大域データ項目を実行可能プログラムの .bss にコピーし、実行可能プログラムの読み取り専用テキストセグメントを同じ状態に保つことをいいます。コピーされるデータにディスプレイスメント再配置が適用されていたり、外部再配置がコピーされるデータへのディスプレイスメントであったりすると、ディスプレイスメント再配置は無効になります。

このようなエラーを検知するために、以下の領域に着目します。

このような問題の診断を助けるため、リンカーは、動的オブジェクトに対してディスプレイスメント再配置が使用されていると、1 つまたは複数の動的 DT_FLAGS_1 フラグを立てます (表 7–44 を参照)。さらに、その可能性のある再配置をリンカーの -z verbose オプションを使って表示することもできます。

たとえば、大域データ項目 bar[]を持つ共有オブジェクトを作成し、このデータ項目にディスプレイスメント再配置が適用されているとします。この項目は、動的実行可能ファイルから参照されると、コピー再配置される可能性があります。リンカーは、この状態に対し次の警告を出します。


$ cc -G -o libfoo.so.1 -z verbose -K pic foo.o
ld: warning: relocation warning: R_SPARC_DISP32: file foo.o: symbol foo: \
    displacement relocation to be applied to the symbol bar: at 0x194: \
    displacement relocation will be visible in output image

次にデータ項目 bar[] を参照するアプリケーションを作成すると、コピー再配置が行われ、ディスプレイスメント再配置が無効にされる可能性があります。リンカーはこの状況を明示的に検出できるため、z verbose オプションが使用されていなくても、次のエラーメッセージを生成します。


$ cc -o prog prog.o -L. -lfoo
ld: warning: relocation error: R_SPARC_DISP32: file foo.so: symbol foo: \
    displacement relocation applied to the symbol bar at: 0x194: \
    the symbol bar is a copy relocated symbol

注 –

リンカーの -d または -r オプションとともに ldd(1) を使用すると、ディスプレイスメント動的フラグによって同じような再配置警告が生成されます。


このようなエラー状態は、再配置するシンボル定義 (オフセット) と再配置のシンボルターゲットを両方ともローカルに置くことによって避けることができます。静的な定義を使用するか、リンカーの範囲指定を使用してください。シンボル範囲の縮小を参照してください。このような再配置の問題は、機能インタフェースを使用して共有オブジェクト内のデータにアクセスすれば、回避することができます。

デバッギングエイド

Solaris リンカーには、デバッギングライブラリが付いています。このライブラリを使用すると、リンク編集プロセスをより詳細に監視できます。このライブラリは、ユーザー自身のアプリケーションまたはライブラリのリンク編集を理解またはデバッグする場合に役立ちます。このライブラリを使用して表示される情報のタイプは、定数のままであると予期されますが、この情報の正確な形式は、リリースごとにわずかに変更される場合があります。

ELF フォーマットを熟知していないと、デバッギング出力の中には見慣れないものがあるかもしれません。しかし、多くのものが一般的な関心を惹くものでしょう。

デバッギングは、-D オプションを使用して実行できます。また、作成された出力はすべて標準エラーに直接送信されます。このオプションは、1 つまたは複数のトークンで拡張し、必要なデバッギングのタイプを指示する必要があります。使用できるトークンは、コマンド行で -D help を入力すれば表示できます。


$ ld -Dhelp
debug:
debug:            For debugging the link-editing of an application:
debug:                  LD_OPTIONS=-Dtoken1,token2 cc -o prog ...
debug:            or,
debug:                  ld -Dtoken1,token2 -o prog ...
debug:            where placement of -D on the command line is significant
debug:            and options can be switched off by prepending with `!'.
debug:
debug:
debug: args       display input argument processing
debug: basic      provide basic trace information/warnings
debug: detail     provide more information in conjunction with other options
debug: entry      display entrance criteria descriptors
debug: files      display input file processing (files and libraries)
debug: got        display GOT symbol information
debug: help       display this help message
debug: libs       display library search paths; detail flag shows actual
debug:              library lookup (-l) processing
debug: map        display map file processing
debug: move       display move section processing
debug: reloc      display relocation processing
debug: sections   display input section processing
debug: segments   display available output segments and address/offset
debug:              processing; detail flag shows associated sections
debug: statistics display processing statistics
debug: strtab     display information about string table compression; detail
debug:              shows layout of string tables
debug: support    display support library processing
debug: symbols    display symbol table processing; detail flag shows
debug:              internal symbol table addition and resolution
debug: tls        display TLS processing info
debug: unused     display unused/unreferenced files; detail flag shows
debug:              unused sections
debug: versions   display version processing

注 –

このリストは一例で、リンカーに有用なオプションを表示しています。正確なオプションについては、リリースごとに異なる場合があります。


ほとんどのコンパイラドライバは、-D オプションの解釈をその前処理フェーズ中に行います。このため、リンカーにこのオプションを渡すためには、LD_OPTIONS 環境変数のメカニズムが適しています。

次の例では、入力ファイルの監視方法を示しています。この構文は、特に、リンク編集中に、配置されたライブラリ、またはアーカイブから抽出された再配置可能オブジェクトを判別する場合に有用です。


$ LD_OPTIONS=-Dfiles cc -o prog main.o -L. -lfoo
............
debug: file=main.o  [ ET_REL ]
debug: file=./libfoo.a  [ archive ]
debug: file=./libfoo.a(foo.o)  [ ET_REL ]
debug: file=./libfoo.a  [ archive ] (again)
............

ここでは、prog のリンク編集を満足させるために、構成要素 foo.o がアーカイブライブラリ libfoo.a から抽出されています。foo.o の抽出が、その他の再配置可能オブジェクトの抽出を認めていないことを検証するために、このアーカイブが 2 回検索されていることに注意してください。“(again)” が複数表示されていることによって、このアーカイブが lorder(1)tsort(1) を使用した並び替えの候補になっていることがわかります。

symbols トークンを使用することにより、どのシンボルによってアーカイブ構成要素が抽出されたか、また、最初のシンボル参照を実行したオブジェクトを判別できます。


$ LD_OPTIONS=-Dsymbols cc -o prog main.o -L. -lfoo
............
debug: symbol table processing; input file=main.o  [ ET_REL ]
............
debug: symbol[7]=foo  (global); adding
debug:
debug: symbol table processing; input file=./libfoo.a  [ archive ]
debug: archive[0]=bar
debug: archive[1]=foo  (foo.o) resolves undefined or tentative symbol
debug:
debug: symbol table processing; input file=./libfoo(foo.o)  [ ET_REL ]
.............

シンボル foo は、main.o によって参照され、リンカーの内部シンボルテーブルに付加されます。このシンボル参照によって、再配置可能オブジェクト foo.o が、アーカイブ libfoo.a から抽出されます。


注 –

この出力は、このマニュアル用に簡素化したものです。


detail トークンを、symbols トークンとともに使用すると、入力ファイル処理中のシンボル解決を監視できます。


$ LD_OPTIONS=-Dsymbols,detail cc -o prog main.o -L. -lfoo
............
debug: symbol table processing; input file=main.o  [ ET_REL ]
............
debug: symbol[7]=foo  (global); adding
debug:   entered  0x000000 0x000000 NOTY GLOB  UNDEF REF_REL_NEED
debug:
debug: symbol table processing; input file=./libfoo.a  [ archive ]
debug: archive[0]=bar
debug: archive[1]=foo  (foo.o) resolves undefined or tentative symbol
debug:
debug: symbol table processing; input file=./libfoo.a(foo.o)  [ ET_REL ]
debug: symbol[1]=foo.c
.............
debug: symbol[7]=bar  (global); adding
debug:   entered  0x000000 0x000004 OBJT GLOB  3     REF_REL_NEED
debug: symbol[8]=foo  (global); resolving [7][0]
debug:       old  0x000000 0x000000 NOTY GLOB  UNDEF main.o
debug:       new  0x000000 0x000024 FUNC GLOB  2     ./libfoo.a(foo.o)
debug:  resolved  0x000000 0x000024 FUNC GLOB  2     REF_REL_NEED
............

main.o からの、オリジナルの未定義シンボル foo が、アーカイブ構成要素 foo.o から抽出されたシンボル定義で上書きされます。このシンボルの詳細情報は、各シンボルの属性に反映されます。

上記の例からわかるように、デバッギングトークンのいくつかを使用すると、豊富な出力が作成されます。入力ファイルのサブセットにかかわるアクティビティだけに関心がある場合には、-D オプションを、リンク編集コマンド行内に直接配置し、オンとオフを切り替えます。次の例では、シンボル処理の表示がオンになるのは、ライブラリlibbar の処理中だけです。


$ ld .... -o prog main.o -L. -Dsymbols -lbar -D!symbols .... 

注 –

リンク編集コマンド行を入手するには、使用しているドライバからコンパイル行を拡張する必要があります。コンパイラドライバを使用する を参照してください。