入力ファイルの処理中に、入力再配置可能オブジェクトからローカルシンボルが出力ファイルイメージに渡されます。大域シンボルはすべて、リンカーの内部に蓄積されます。再配置可能オブジェクトから大域シンボルが供給されると、この内部シンボルテーブル内が検索されます。過去の入力ファイルで、同じ名前のシンボルに遭遇したことがある場合には、シンボル解決プロセスが呼び出されます。このシンボル解決プロセスは、2 つのエントリのうちどちらを保持するかを決定します。
入力ファイル処理の完了時、シンボル解決中に深刻なエラー状態が発生しなかった場合は、リンカーは、未解決のシンボル参照が残っていないかどうか判別します。未解決のシンボル参照があると、リンク編集は強制終了します。
最後に、リンカーの内部シンボルテーブルが、作成されるイメージのシンボルテーブルに追加されます。
次の項では、シンボル解決と未定義シンボルの処理について詳しく説明します。
シンボル解決は、簡単で直感的に分かるものから、複雑で当惑するようなものまで、すべての範囲を実行します。解決は、リンカーによって自動的に実行されるか、警告診断プログラムを伴って表示されるか、またはその結果、重大なエラー状態になります。
2 つのシンボルの解決は、シンボルの属性、シンボルを入手したファイルのタイプおよび生成されるファイルのタイプによって異なります。シンボルの属性についての詳細は、シンボルテーブルセクションを参照してください。ただし、以下に説明するシンボルタイプは、識別する価値のある 3 つの基本的なシンボルタイプです。
一時的シンボル –
このシンボルは、ファイル内で作成されましたが、まだサイズが決められていないか、または記憶領域内に割り当てられていません。このようなシンボルは、初期化されていない
C シンボル、または FORTRAN
COMMON ブロックとしてファイル内に表示されます。
シンボル解決の最も単純な形では、優先度が比較されます。つまり、定義シンボルは一時的シンボルよりも優先され、一時的シンボルは未定義シンボルよりも優先されます。
次の 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.c と bar.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 オプションを指定すると、リンク編集コマンド行からシンボル参照を作成するためのメカニズムが使用できます。このオプションは、リンク編集をすべてアーカイブから実行する場合に使用でき、また、複数のアーカイブから抽出するオブジェクトの選択における柔軟性を向上させることができます。アーカイブの抽出については、アーカイブ処理の項を参照してください。
たとえば、動的実行可能プログラムを、シンボル foo と bar への参照を実行する再配置可能オブジェクト 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 ]; |
このシンボル定義のセットのラベルは、もしあれば、イメージ内のバージョン定義を識別できます。第 5 章「アプリケーションバイナリインタフェースとバージョン管理」を参照してください。
生成される出力ファイル内のシンボルのバインディングの可視性を示しています。mapfile で定義されたすべてのシンボルは、リンク編集プロセス中に、スコープ内で global (大域) として処理されます。つまり、これらのシンボルは、入力ファイルのいずれかから入手された、同じ名前の他のシンボルに対して解決されます。以下の定義と別名は、作成されるオブジェクト内におけるシンボルの可視性を定義します。
このスコープのシンボルは、ほかの外部オブジェクトから見えます。このタイプのシンボルに対するオブジェクト内からの参照は実行時に結合されるため、介入が可能となります。
このスコープのシンボルは、ほかの外部オブジェクトから見えます。これらのシンボルに対するオブジェクト内からの参照はリンク編集時に結合されるため、実行時の介入は防止されます。このスコープ定義には、シンボルに STV_PROTECTED 可視性が指定された場合と同じ効果があります。表 7–24を参照してください。
このスコープのシンボルは、ローカル結合されるシンボルに縮小されます。このスコープのシンボルは、ほかの外部オブジェクトから見えません。このスコープ定義には、シンボルに STV_HIDDEN 可視性が指定された場合と同じ効果があります。表 7–24を参照してください。
このスコープのシンボルは hidden です。これらのシンボルテーブルのエントリは削除されます。
要求されたシンボルの名前です。この名前の後にシンボル属性 type、 value、size、または extern のいずれかがついていない場合、シンボル参照が作成されます。この参照は、この項の最初に説明した -u オプションを使用して生成する参照とまったく同じものです。このシンボル名にシンボル属性が付いている場合には、シンボル定義は、関連する属性を使用して生成されます。
local スコープ内では、このシンボル名は、特別な「auto-reduction」(自動縮小) 指示語「*」として定義できます。この指示語を使用すると、すべての大域シンボル (mapfile 内に global と明示的に定義されていないもの) は、生成される動的オブジェクトファイル内でローカル結合されます。
シンボルのタイプ属性を示します。この属性は、data、function、または COMMON のいずれかです。最初の 2 つのタイプ属性の結果は、絶対的なシンボル定義になります 。シンボルテーブルセクションを参照してください。 後者のタイプ属性の結果は、一時的シンボル定義になります。
シンボルの値属性を示し、Vnumber の書式です。
シンボルのサイズ属性を示し、Vnumber の書式です。
このキーワードは、シンボルに以下の追加属性を提供します。
シンボルが、作成されるオブジェクトの外部で定義されていることを示します。この属性は、 個々の直接参照または間接参照を確立するために、DIRECT 属性または NODIRECT 属性と併用できます。また、このオプションを使用して、-z defs オプションで示される未定義シンボルを抑制することもできます。
このシンボルを直接結合する必要があることを示します。この属性は、外部シンボルへの結合を制御する EXTERN 属性と併用できます。直接結合を参照してください。
このシンボルを直接結合してはならないことを示します。この状態は、作成されるオブジェクト内からの参照と外部参照に適用されます。この属性は、外部シンボルへの結合を制御する EXTERN 属性と併用できます。直接結合を参照してください。
この定義が継承するバージョン定義を示します。第 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.c と bar.c から生成するとします。ファイル foo.c には、他のユーザーも使用できるように設定するサービスを提供する大域シンボル foo が組み込まれています。ファイル bar.c には、共有オブジェクトの根底となるインプリメンテーションを提供するシンボル bar と str が組み込まれています。簡単に作成された共有オブジェクトは、通常、これらの 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 への参照は、共有オブジェクトによって提供されたインプリメンテーションに結合されます。
大域結合により、シンボル bar と str への直接参照も可能です。ただし、これは危険な結果を招く場合があります。関数 foo の基礎となるインプリメンテーションは、後から変更することがあるためです。それが原因で知らないうちに、bar または str に結合された既存のアプリケーションが失敗または誤作動を起こす可能性があります。
また、シンボル bar と str を大域結合すると、同じ名前のシンボルによって割り込まれる可能性があります。共有オブジェクト内へのシンボルの割り込みについては、単純な解決 の項で説明しています。この割り込みは、意図的に行うことができ、これを使用することにより、共有オブジェクトが提供する目的の機能を取り囲むことができます。また反対に、この割り込みは、同じ共通のシンボル名をアプリケーションと共有オブジェクトの両方に使用した結果として、知らないうちに実行される場合もあります。
共有オブジェクトを開発する場合は、シンボル bar と str の範囲をローカル結合に縮小して、このような事態から保護することができます。次の例のシンボル bar と str は、共有オブジェクトのインタフェースの一部としては使用できません。そのため、これらのシンボルは、外部のオブジェクトによって参照されることができないか、割り込みはできません。ユーザーは、インタフェースをこの共有オブジェクト用に効果的に定義できます。インプリメンテーションの基礎となる詳細を隠している間は、このインタフェースを管理できます。
$ 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 |
このようなシンボル範囲の縮小には、この他にもパフォーマンスにおける利点があります。実行時に必要だったシンボル bar と str に対するシンボルの再配置は、現在は、関連する再配置に縮小されます。これにより、実行時の、共有オブジェクトの初期設定と処理のオーバーヘッドが削減されます。シンボル再配置のオーバーヘッドの詳細は、再配置を実行する場合 を参照してください。
リンク編集間で処理されるシンボル数が多くなると、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.1 も mapfile 指示語の一部として定義しています。このバージョン名により、ファイルのシンボルインタフェースを定義する、内部バージョン定義が確立されます。バージョン定義はできるだけ作成してください。バージョン定義によって、ファイルの展開全体を通して使用できる、内部バージョンメカニズムの基礎が形成されます。第 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 を使用して表示できます。