Go to main content
Oracle® Solaris 11.3 リンカーとライブラリガイド

印刷ビューの終了

更新: 2015 年 10 月
 
 

シンボルの処理

シンボルは、局所大域に分類できます。シンボルの可視性を参照してください。

局所シンボルは、入力ファイルの処理中に入力再配置可能オブジェクトファイルから構築中の出力オブジェクトに検査なしでコピーされます。

すべての入力再配置可能オブジェクトから渡される大域シンボルと外部依存関係から渡される大域シンボルは、シンボル解決と呼ばれるプロセスで分析および結合されます。リンカーは、各シンボルを検出された順に内部シンボルテーブルに配置します。同じ名前のシンボルが以前のオブジェクトによって提供され、シンボルテーブル内にすでに存在する場合は、シンボル解決プロセスによって 2 つのシンボルのどちらを保持するかが決定されます。このプロセスの副作用として、リンカーは外部オブジェクトの依存関係への参照を確立する方法を決定します。

入力ファイルの処理が正常終了すると、リンカーはシンボル可視性の調整を適用し、未解決のシンボル参照が残っているかどうかを判定します。シンボル解決の致命的エラーが発生した場合や、未解決のシンボル参照が残っている場合は、リンク編集が終了します。最後に、リンカーの内部シンボルテーブルが、作成されるイメージのシンボルテーブルに追加されます。

以降のセクションでは、シンボルの可視性、シンボル解決、および未定義シンボルの処理について詳しく説明します。

シンボルの可視性

シンボルは、局所大域に分類できます。局所シンボルは、シンボル定義が含まれるオブジェクト以外のオブジェクトから参照できません。デフォルトでは、局所シンボルは入力再配置可能オブジェクトファイルから構築中の出力オブジェクトにコピーされます。代わりに、局所シンボルを出力オブジェクトから削除できます。シンボル削除を参照してください。

大域シンボルは、シンボル定義が含まれるオブジェクト以外のオブジェクトからも参照できます。大域シンボルは、収集と解決のあとで、出力オブジェクト内に作成されるシンボルテーブルに追加されます。すべての大域シンボルがまとめて処理および解決されますが、それらの最終的な可視性は調整できます。大域シンボルには、追加の可視性属性を定義できます。表 35 を参照してください。さらに、mapfile シンボル指令を使用して、リンク編集中にシンボルの可視性を割り当てることもできます。表 9 を参照してください。これらの可視性属性 (および指令) により、出力オブジェクトへの書き込み時に可視性が調整された大域シンボルを生成できます。

再配置可能オブジェクトを作成すると、すべての可視性属性および指令が出力オブジェクトに記録されます。ただし、これらの属性によって暗黙的に定義された可視性の変更は適用されません。代わりに、これらのオブジェクトを入力として読み取る動的オブジェクトの次のリンク編集まで、可視性の処理が延期されます。特殊なケースでは、–B reduce オプションを使用して、可視性属性および指令をただちに強制的に解釈できます。

動的実行可能ファイル (または共有オブジェクト) を作成すると、シンボルがシンボルテーブルに書き込まれる前に、シンボル可視性の属性および指令が適用されます。可視性属性により、シンボルが大域のままであり、シンボル縮小の手法による影響を受けないことを保証できます。可視性の属性および指令によって、局所に降格される大域シンボルを生成することもできます。この後者の手法がもっともよく使用されるのは、オブジェクトのエクスポートされたインタフェースを明示的に定義する場合です。シンボル範囲の縮小を参照してください。

シンボル解決

シンボル解決は、簡単で直感的に分かるものから、複雑で当惑するようなものまで、すべての範囲を実行します。ほとんどのシンボル解決は、リンカーによって自動的に実行されます。ただし、警告診断を伴う再配置や、致命的なエラー状態の原因となる再配置もあります。

もっとも一般的で単純な解決は、あるオブジェクトから別のオブジェクト内部のシンボル定義へのシンボル参照の結合です。この結合は、2 つの再配置可能オブジェクト間、および再配置可能オブジェクトと共有オブジェクト依存関係内で検出された最初の定義の間で発生する場合があります。通常、複雑な解決は、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;

int d_foo()
{
        return (u_foo(u_bar, t_bar, d_bar));
}
$ cc -o main.o -c main.c
$ elfdump -s main.o

Symbol Table Section:  .symtab
    index  value   size  type bind oth ver shndx          name
    ....
      [7]      0      0  FUNC GLOB  D    0 UNDEF          u_foo
      [8]   0x10   0x40  FUNC GLOB  D    0 .text          d_foo
      [9]    0x4    0x4  OBJT GLOB  D    0 COMMON         t_bar
     [10]      0    0x4  NOTY GLOB  D    0 UNDEF          u_bar
     [11]      0    0x4  OBJT GLOB  D    0 .data          d_bar

単純な解決

単純なシンボル解決は、もっとも一般的です。この場合、類似する特徴を持ち、どちらかが優先される 2 つのシンボルが検出されます。このシンボル解決は、リンカーによって自動的に実行されます。たとえば、同じ結合を持つシンボルがあり、1 つのファイルからのシンボル参照が、別のファイルの定義または一時的シンボル定義に結合されているとします。あるいは、あるファイルからの一時的シンボル定義は、ほかのファイルからの定義シンボルの定義に結合されます。この解決は、2 つの再配置可能オブジェクト間、および再配置可能オブジェクトと共有オブジェクト依存関係内で検出された最初の定義の間で発生する場合があります。

解決されるシンボルは、大域結合またはウィーク結合されます。再配置可能オブジェクトの処理中は、ウィーク結合の方が、大域結合よりも優先度が低くなります。ウィークシンボル定義は同じ名前の大域定義によって暗黙のうちにオーバーライドされます。

単純なシンボル解決のもう 1 つの形式である「割り込み」は、再配置可能オブジェクトと共有オブジェクト間、または複数の共有オブジェクト間で発生します。この場合、シンボルが複数回定義されていれば、再配置可能オブジェクト、または複数の共有オブジェクト間の最初の定義がリンカーによって暗黙のうちに採用されます。再配置可能オブジェクトの定義、または最初の共有オブジェクトの定義は、ほかのすべての定義上に割り込みを行うといわれます。この割り込みを使用して、別の共有オブジェクトが提供する機能をオーバーライドすることができます。再配置可能オブジェクトと共有オブジェクトの間、または複数の共有オブジェクト間で発生する複数回定義されたシンボルは、同一に扱われます。シンボルのウィーク結合や大域結合は、これとは無関係です。最初の定義を解決することにより、シンボルの結合に関係なく、リンカーと実行時リンカーの両方が一貫して動作します。

リンカーの –m オプションを使用して、割り込みされるすべてのシンボル参照のリストを、セクションのロードアドレス情報とともに標準出力に書き込んでください。

複雑な解決

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

$ cat foo.c
int array[1];
$ cat bar.c
int array[2] = { 1, 2 };
$ ld -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 オプションを使用すると、診断を抑制できます。

異なる属性のもう 1 つの形式は、シンボルのタイプの違いです。次の例では、シンボル bar() は、データ項目と関数の両方として定義されています。

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

int 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
int bar()
{
        return (0);
}
$ ld -r -o temp.o foo.c bar.c
ld: fatal: symbol 'bar' is multiply-defined:
    (file foo.o and file bar.o);

foo.cbar.c は、シンボル bar の定義が競合しています。リンカーは、どちらを優先すべきか判別できないため、通常はエラーメッセージを出力して終了します。

シンボルは複数回定義しないようにしてください。シンプルなコーディングシナリオでは、リンカーの –z muldefs オプションを使用することで複数のシンボル定義のエラーを回避できます。このオプションでは、多重定義シンボルの最初の定義を出力ファイルに伝達し、多重定義シンボルのその他の定義を破棄できます。多重定義項目へのすべての参照がその項目の大域シンボル名を使用している場合、すべての参照は多重定義シンボルの最初のインスタンスに解決されます。

ただし、特殊なコンパイラオプションまたはコンパイラの高度な最適化を使用することで、–z muldefs オプションの使用を回避できます。これらの条件の下、コンパイラはローカルセクションシンボル参照の代わりに大域シンボル参照を使用できます。これにより、すべての参照が単一の大域シンボルに指定されるのではなく、多重定義項目の個々の項目が参照され続ける場合があります。この不整合によって複数の項目が異なる値を持ち、予期しないプログラムの動作を引き起こすことがあります。柔軟性を高めるため、複数回のシンボル定義は避けるべきです。

未定義シンボル

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

実行可能ファイルの作成

リンカーが実行可能出力ファイルを生成する際のデフォルト動作は、「未定義のままのシンボルが存在するかぎり、適切なエラーメッセージを出力して処理を終了する」というものです。次のように、再配置可能オブジェクト内のシンボル参照が、シンボル定義と絶対に一致しない場合に、シンボルは定義されないままの状態になります。

$ cat main.c
extern int foo();

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

同様に、共有オブジェクトを使って動的実行可能ファイルを作成する場合、未解決のままのシンボル定義が存在していると、未定義シンボルエラーが発生します。

$ cat foo.c
extern int bar;
int 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

前の例のように未定義シンボルを許可するには、リンカーの –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

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

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

このため、このタイプの結合は致命的とみなされます。暗黙的参照は、prog のリンク編集中に直接ライブラリを参照することで明示的に行います。この例で示した重大なエラーメッセージ内に必要な参照のヒントがあります。

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

リンカーが共有オブジェクト出力ファイルを生成する場合、未定義シンボルをリンク編集の後も残すことができます。このデフォルト動作により、共有オブジェクトが、依存関係として共有オブジェクトを定義する動的実行可能ファイルからシンボルをインポートできます。

リンカーの –z defs オプションを使用すると、未定義シンボルが残っていた場合に、強制的に致命的なエラーにすることができます。共有オブジェクトを作成するときには、このオプションの使用をお勧めします。アプリケーションからシンボルを参照する共有オブジェクトは、extern mapfile 指令でシンボルを定義するとともに、–z defs オプションを使用できます。SYMBOL_SCOPE および SYMBOL_VERSION ディレクティブを参照してください。

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

ウィークシンボル

ウィークシンボルは歴史的に、割り込みを回避したり、オプション機能をテストしたりするために使用されてきました。ただし、近年のプログラミング環境ではウィークシンボルは脆弱で信頼性が低いことが経験によって示されており、使用は推奨されません。

ウィークシンボル別名はシステム共有オブジェクト内で頻繁に使用されてきました。その意図は、代替のインタフェース名を提供することであり、典型的には「_」文字が前に付いたシンボル名です。このエイリアス名は、アプリケーションがシンボル名の独自の実装をエクスポートすることによる割り込みの問題を回避するために、他のシステム共有オブジェクトから参照できます。現実的には、この手法は複雑すぎることが証明され、整合性を持たずに使用されていました。最近の Oracle Solaris バージョンでは、システムオブジェクト間の明示的な結合を直接結合によって確立します。Chapter 6, 直接結合を参照してください。

ウィークシンボル参照は、実行時でのインタフェースの存在をテストするためにしばしば使用されていました。この手法は、構築環境および実行環境に制限を設け、コンパイラ最適化によって回避できます。dlsym(3C)RTLD_DEFAULT または RTLD_PROBE ハンドルと一緒に使用することで、シンボルの存在をテストするための一貫性のある堅牢な手段が提供されます。機能のテストを参照してください。

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

入力ファイルの追加は、通常、その追加の順に出力ファイルに表示されます。一時的シンボルはこの規則の例外であり、シンボルが完全に解決されるまで完全には定義されません。出力ファイル内の一時的シンボルの順番は、追加順にならない場合があります。

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

$ cat foo.c
char One_array[0x10];
char Two_array[0x20];
char Three_array[0x30];
$ cc -o libfoo.so -G -Kpic foo.c
$ elfdump -sN.dynsym libfoo.so | grep array | sort -k 2,2
    [11]   0x10614  0x20  OBJT GLOB  D    0 .bss           Two_array
     [3]   0x10634  0x30  OBJT GLOB  D    0 .bss           Three_array
     [4]   0x10664  0x10  OBJT GLOB  D    0 .bss           One_array

シンボルをアドレス順にソートすると、その出力順序はソース内で定義された順序と異なることがわかります。反対に、これらのシンボルを初期化されたデータ項目として定義すると、入力ファイル内のこれらのシンボルの相対順序は、確実に出力ファイル内に引き継がれます。

$ cat foo.c
char A_array[0x10] = { 0 };
char B_array[0x20] = { 0 };
char C_array[0x30] = { 0 };
$ cc -o libfoo.so -G -Kpic foo.c
$ elfdump -sN.dynsym libfoo.so | grep array | sort -k 2,2
     [4]   0x10614  0x10  OBJT GLOB  D    0 .data          One_array
    [11]   0x10624  0x20  OBJT GLOB  D    0 .data          Two_array
     [3]   0x10644  0x30  OBJT GLOB  D    0 .data          Three_array

追加シンボルの定義

入力ファイルから提供されるシンボルのほかに、追加の大域シンボル参照や大域シンボル定義をリンク編集に対して指定できます。もっとも簡単な形式で、シンボル参照は、リンカーの –u オプションを使用して作成できます。リンカーの –M オプションと関連 mapfile を使用すると柔軟性が高まります。この mapfile を使用すると、大域シンボル参照およびさまざまな大域シンボル定義を定義できます。可視性や型などのシンボルの属性を指定できます。使用可能なオプションの詳細な説明については、SYMBOL_SCOPE および SYMBOL_VERSION ディレクティブを参照してください。

-u オプションを使用した追加シンボルの定義

–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 の直接的または間接的な参照は行いません。lib1.abar を参照する場合、処理中に再配置可能オブジェクト bar.olib1.a から抽出されます。アーカイブを処理するリンカーのマルチパスについては、アーカイブ処理を参照してください。

シンボル参照の定義

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

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

void foo()
{
        (void) printf("foo: called from lib.a\n");
}
$ cat bar.c
#include    <stdio.h>

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

void main()
{
        foo();
        bar();
}
$ cc -c foo.c bar.c main.c
$ ar -rc lib.a foo.o bar.o main.o
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        local:
                foo;
                bar;
        global:
                main;
};
$ cc -o prog -M mapfile lib.a
$ prog
foo: called from lib.a
bar: called from lib.a
$ elfdump -sN.symtab prog | egrep 'main$|foo$|bar$'
    [29]   0x10f30  0x24  FUNC LOCL  H    0 .text          bar
    [30]   0x10ef8  0x24  FUNC LOCL  H    0 .text          foo
    [55]   0x10f68  0x24  FUNC GLOB  D    0 .text          main

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

絶対シンボルの定義

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

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

extern int foo();
extern int bar;

void main()
{
        (void) printf("&foo = 0x%p\n", &foo);
        (void) printf("&bar = 0x%p\n", &bar);
}
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=FUNCTION; VALUE=0x400 };
                bar     { TYPE=DATA;     VALUE=0x800 };
};
$ cc -o prog -M mapfile main.c
$ prog
&foo = 0x400
&bar = 0x800
$ elfdump -sN.symtab prog | egrep 'foo$|bar$'
    [45]     0x800     0  OBJT GLOB  D    0 ABS            bar
    [69]     0x400     0  FUNC GLOB  D    0 ABS            foo

入力ファイルから入手される場合、関数のシンボル定義またはデータ項目は、通常、データストレージの要素に関連しています。mapfile 定義は、このデータストレージを構成するためには不十分であるため、これらのシンボルは、絶対値として残しておく必要があります。size が関連付けられるが、value関連付けられない単純な mapfile 定義では、データストレージが作成されます。この場合、シンボル定義にはセクションインデックスが伴います。ただし、mapfile 定義に value を関連付けると、絶対シンボルが作成されます。シンボルが共有オブジェクト内で定義される場合、絶対定義は避けるようにしてください。シンボル定義の増強を参照してください。

一時的シンボルの定義

mapfileCOMMON または一時的シンボルを定義する場合にも使用できます。ほかのタイプのシンボル定義とは違って、一時的シンボルは、ファイル内のストレージを占有しませんが、実行時に割り当てるストレージの定義は行います。そのため、このタイプのシンボル定義は、作成される出力ファイルのストレージ割り当ての一因となります。

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

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

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

extern int foo;

int bar[0x10];

void main()
{
        (void) printf("&foo = 0x%p\n", &foo);
        (void) printf("&bar = 0x%p\n", &bar);
}
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=COMMON; VALUE=0x4;   SIZE=0x200 };
                bar     { TYPE=COMMON; VALUE=0x102; SIZE=0x40 };
};
$ cc -o prog -M mapfile main.c
ld: warning: symbol 'bar' has differing alignments:
    (file mapfile value=0x102; file main.o value=0x4);
    largest value applied
$ prog
&foo = 0x21264
&bar = 0x21224
$ elfdump -sN.symtab prog | egrep 'foo$|bar$'
    [45]   0x21224   0x40  OBJT GLOB  D    0 .bss           bar
    [69]   0x21264  0x200  OBJT GLOB  D    0 .bss           foo

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

シンボル定義の増強

共有オブジェクト内での絶対データシンボルの作成は避けるべきです。通常、動的実行可能ファイルから、共有オブジェクト内のデータ項目への外部参照には、コピー再配置の作成が必要になります。コピー再配置を参照してください。このような再配置を行う場合は、データ項目をデータストレージと関連付けるべきです。この関連付けは、再配置可能なオブジェクトファイル内にシンボルを定義することで行うことができます。この関連付けは、mapfile 内でシンボルを size 宣言あり、 value 宣言なしで定義しても行うことができます。SYMBOL_SCOPE および SYMBOL_VERSION ディレクティブを参照してください。

データシンボルにはフィルタを適用できます。フィルタとしての共有オブジェクトを参照してください。このようなフィルタ適用を行うため、オブジェクトファイル定義は mapfile 定義で増強できます。次の例では、関数定義とデータ定義を含むフィルタを作成します。

$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=FUNCTION;       FILTER=filtee.so.1 };
                bar     { TYPE=DATA; SIZE=0x4; FILTER=filtee.so.1 };
        local:
                *;
};
$ cc -o filter.so.1 -G -Kpic -h filter.so.1 -M mapfile -R.
$ elfdump -sN.dynsym filter.so.1 | egrep 'foo|bar'
     [1]   0x105f8     0x4  OBJT GLOB  D    1 .data          bar
     [7]         0       0  FUNC GLOB  D    1 ABS            foo
$ elfdump -y filter.so.1 | egrep 'foo|bar'
     [1]  F        [0] filtee.so.1        bar
     [7]  F        [0] filtee.so.1        foo

実行時に、外部オブジェクトからこれらのシンボルのいずれかへの参照は、「フィルティー」内の定義に解決されます。

シンボル範囲の縮小

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 libfoo.so.1 -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
    [41]     0x560    0x18  FUNC GLOB  D    0 .text          bar
    [44]     0x520    0x2c  FUNC GLOB  D    0 .text          foo
    [45]   0x106b8     0x4  OBJT GLOB  D    0 .data          str

これで、libfoo.so.1 により提供された機能を、別のアプリケーションのリンク編集の一部として使用できます。シンボル foo への参照は、共有オブジェクトによって提供された実装に結合されます。

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

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

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

$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        local:
                bar;
                str;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
    [24]     0x548    0x18  FUNC LOCL  H    0 .text          bar
    [25]   0x106a0     0x4  OBJT LOCL  H    0 .data          str
    [45]     0x508    0x2c  FUNC GLOB  D    0 .text          foo

このようなシンボル範囲の縮小には、このほかにもパフォーマンスにおける利点があります。実行時に必要だったシンボル barstr に対するシンボルの再配置は、現在は関連する再配置に縮小されます。シンボル再配置のオーバーヘッドの詳細は、再配置が実行されるときを参照してください。

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

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
               foo;
        local:
               *;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
    [26]     0x570    0x18  FUNC LOCL  H    0 .text          bar
    [27]   0x106d8     0x4  OBJT LOCL  H    0 .data          str
    [50]     0x530    0x2c  FUNC GLOB  D    0 .text          foo

この例では、mapfile 指令の一部としてバージョン名 (ISV_1.1) も定義しています。このバージョン名により、ファイルのシンボルインタフェースを定義する、内部バージョン定義が確立されます。バージョン定義はできるだけ作成してください。バージョン定義によって、ファイルの展開全体を通して使用できる、内部バージョンメカニズムの基礎が形成されます。Chapter 10, インタフェースおよびバージョン管理を参照してください。


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

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

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
};
$ cc -o libfoo.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

–B local オプションを使用して、コマンド行から自動縮小指令「*」を表明することができます。前の例は、次のようにコンパイルすることもできます。

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

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

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
        local:
                *;
};
$ ld -o libfoo.o -M mapfile -r foo.o bar.o
$ elfdump -s libfoo.o | egrep 'foo$|bar$|str$'
    [29]      0x10    0x2c  FUNC GLOB  D    2 .text          foo
    [30]         0     0x4  OBJT GLOB  H    0 .data          str

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

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

$ ld -o libfoo.so.1 -G libfoo.o
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
    [24]     0x508    0x18  FUNC LOCL  H    0 .text          bar
    [25]   0x10644     0x4  OBJT LOCL  H    0 .data          str
    [42]     0x4c8    0x2c  FUNC GLOB  D    0 .text          foo

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

$ ld -o libfoo.o -M mapfile -B reduce -r foo.o bar.o
$ elfdump -sN.symtab libfoo.o | egrep 'foo$|bar$|str$'
    [20]      0x50    0x18  FUNC LOCL  H    0 .text          bar
    [21]         0     0x4  OBJT LOCL  H    0 .data          str
    [30]      0x10    0x2c  FUNC GLOB  D    2 .text          foo

シンボル削除

シンボル縮小の拡張の 1 つは、オブジェクトのシンボルテーブルから特定のシンボルエントリを削除することです。局所シンボルは、オブジェクトの .symtab シンボルテーブルだけで管理されます。このテーブル全体は、リンカーの –z strip-class オプションを使用して、またはリンク編集後に strip(1) を使用してオブジェクトから取り除くことができます。しかし、.symtab シンボルテーブルは削除しないで、特定の局所シンボルだけを削除したいこともあります。

シンボルの削除は、mapfile キーワード ELIMINATE を使用して実行できます。local 指令と同様に個別にシンボルを定義することも、特殊な自動削除指令「*」としてシンボル名を定義することもできます。次の例では、前述のシンボル縮小の例で使用したシンボル bar を削除しています。

$ cat mapfile
$mapfile_version 2
SYMBOL_VERSION ISV_1.1 {
        global:
                foo;
        local:
                str;
        eliminate:
                *;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$'
    [26]   0x10690     0x4  OBJT LOCL  H    0 .data          str
    [44]     0x4e8    0x2c  FUNC GLOB  D    0 .text          foo

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

外部結合

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

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

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

文字列テーブルの圧縮

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

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