ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris 11.1 リンカーとライブラリガイド Oracle Solaris 11.1 Information Library (日本語) |
7. システムのパフォーマンスを最適化するオブジェクトの構築
パート IV ELF アプリケーションバイナリインタフェース
シンボルは、局所と大域に分類できます。「シンボルの可視性」を参照してください。
局所シンボルは、入力ファイルの処理中に入力再配置可能オブジェクトファイルから構築中の出力オブジェクトに検査なしでコピーされます。
すべての入力再配置可能オブジェクトから渡される大域シンボルと外部依存関係から渡される大域シンボルは、シンボル解決と呼ばれるプロセスで分析および結合されます。リンカーは、各シンボルを検出された順に内部シンボルテーブルに配置します。同じ名前のシンボルが以前のオブジェクトによって提供され、シンボルテーブル内にすでに存在する場合は、シンボル解決プロセスによって 2 つのシンボルのどちらを保持するかが決定されます。このプロセスの副作用として、リンカーは外部オブジェクトの依存関係への参照を確立する方法を決定します。
入力ファイルの処理が正常終了すると、リンカーはシンボル可視性の調整を適用し、未解決のシンボル参照が残っているかどうかを判定します。シンボル解決の致命的エラーが発生した場合や、未解決のシンボル参照が残っている場合は、リンク編集が終了します。最後に、リンカーの内部シンボルテーブルが、作成されるイメージのシンボルテーブルに追加されます。
以降のセクションでは、シンボルの可視性、シンボル解決、および未定義シンボルの処理について詳しく説明します。
シンボルは、局所と大域に分類できます。局所シンボルは、シンボル定義が含まれるオブジェクト以外のオブジェクトから参照できません。デフォルトでは、局所シンボルは入力再配置可能オブジェクトファイルから構築中の出力オブジェクトにコピーされます。代わりに、局所シンボルを出力オブジェクトから削除できます。「シンボル削除」を参照してください。
大域シンボルは、シンボル定義が含まれるオブジェクト以外のオブジェクトからも参照できます。大域シンボルは、収集と解決のあとで、出力オブジェクト内に作成されるシンボルテーブルに追加されます。すべての大域シンボルがまとめて処理および解決されますが、それらの最終的な可視性は調整できます。大域シンボルには、追加の可視性属性を定義できます。表 12-21 を参照してください。さらに、mapfile シンボル指令を使用して、リンク編集中にシンボルの可視性を割り当てることもできます。表 8-8 を参照してください。これらの可視性属性 (および指令) により、出力オブジェクトへの書き込み時に可視性が調整された大域シンボルを生成できます。
再配置可能オブジェクトを作成すると、すべての可視性属性および指令が出力オブジェクトに記録されます。ただし、これらの属性によって暗黙的に定義された可視性の変更は適用されません。代わりに、これらのオブジェクトを入力として読み取る動的オブジェクトの次のリンク編集まで、可視性の処理が延期されます。特殊なケースでは、-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] 0x00000000 0x00000000 FUNC GLOB D 0 UNDEF u_foo [8] 0x00000010 0x00000040 FUNC GLOB D 0 .text d_foo [9] 0x00000004 0x00000004 OBJT GLOB D 0 COMMON t_bar [10] 0x00000000 0x00000004 NOTY GLOB D 0 UNDEF u_bar [11] 0x00000000 0x00000004 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); ld: fatal: File processing errors. No output written to int.o
foo.c と bar.c に含まれるシンボル bar の定義が互いに矛盾しています。リンカーは、どちらを優先すべきか判別できないため、通常はエラーメッセージを出力して終了します。リンカーの -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. No output written to prog
同様に、共有オブジェクトを使って動的実行可能ファイルを作成する場合、未解決のままのシンボル定義が存在していると、未定義シンボルエラーが発生します。
$ 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. 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 に依存しています。したがって、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 バージョンでは、システムオブジェクト間の明示的な結合を直接結合によって確立します。第 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] 0x00010614 0x00000020 OBJT GLOB D 0 .bss Two_array [3] 0x00010634 0x00000030 OBJT GLOB D 0 .bss Three_array [4] 0x00010664 0x00000010 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] 0x00010614 0x00000010 OBJT GLOB D 0 .data One_array [11] 0x00010624 0x00000020 OBJT GLOB D 0 .data Two_array [3] 0x00010644 0x00000030 OBJT GLOB D 0 .data Three_array
入力ファイルから提供されるシンボルのほかに、追加の大域シンボル参照や大域シンボル定義をリンク編集に対して指定できます。もっとも簡単な形式で、シンボル参照は、リンカーの -u オプションを使用して作成できます。リンカーの -M オプションと関連 mapfile を使用すると柔軟性が高まります。この mapfile を使用すると、大域シンボル参照およびさまざまな大域シンボル定義を定義できます。可視性や型などのシンボルの属性を指定できます。使用可能なオプションの詳細な説明については、「SYMBOL_SCOPE/SYMBOL_VERSION 指令」を参照してください。
-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 の直接的または間接的な参照は行いません。lib1.a が bar を参照する場合、処理中に再配置可能オブジェクト bar.o も lib1.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] 0x00010f30 0x00000024 FUNC LOCL H 0 .text bar [30] 0x00010ef8 0x00000024 FUNC LOCL H 0 .text foo [55] 0x00010f68 0x00000024 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] 0x00000800 0x00000000 OBJT GLOB D 0 ABS bar [69] 0x00000400 0x00000000 FUNC GLOB D 0 ABS foo
入力ファイルから入手される場合、関数のシンボル定義またはデータ項目は、通常、データストレージの要素に関連しています。mapfile 定義は、このデータストレージを構成するためには不十分であるため、これらのシンボルは、絶対値として残しておく必要があります。size が関連付けられるが、value は関連付けられない単純な mapfile 定義では、データストレージが作成されます。この場合、シンボル定義にはセクションインデックスが伴います。ただし、mapfile 定義に value を関連付けると、絶対シンボルが作成されます。シンボルが共有オブジェクト内で定義される場合、絶対定義は避けるようにしてください。「シンボル定義の増強」を参照してください。
mapfile は COMMON または一時的シンボルを定義する場合にも使用できます。ほかのタイプのシンボル定義とは違って、一時的シンボルは、ファイル内のストレージを占有しませんが、実行時に割り当てるストレージの定義は行います。そのため、このタイプのシンボル定義は、作成される出力ファイルのストレージ割り当ての一因となります。
一時的シンボルの特徴は、ほかのシンボルタイプとは異なり、その値の属性によって、その配列要件が示される点です。そのため、リンク編集の入力ファイルから入手される一時的定義の再配列に 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] 0x00021224 0x00000040 OBJT GLOB D 0 .bss bar [69] 0x00021264 0x00000200 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] 0x000105f8 0x00000004 OBJT GLOB D 1 .data bar [7] 0x00000000 0x00000000 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.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 libfoo.so.1 -G foo.c bar.c $ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$' [41] 0x00000560 0x00000018 FUNC GLOB D 0 .text bar [44] 0x00000520 0x0000002c FUNC GLOB D 0 .text foo [45] 0x000106b8 0x00000004 OBJT GLOB D 0 .data str
これで、libfoo.so.1 により提供された機能を、別のアプリケーションのリンク編集の一部として使用できます。シンボル foo への参照は、共有オブジェクトによって提供されたインプリメンテーションに結合されます。
大域結合により、シンボル bar と str への直接参照も可能です。ただし、この可視性は危険な結果を招く場合があります。関数 foo の基礎となるインプリメンテーションは、後から変更することがあるためです。それが原因で知らないうちに、bar または str に結合された既存のアプリケーションが失敗または誤作動を起こす可能性があります。
また、シンボル bar と str を大域結合すると、同じ名前のシンボルによって割り込まれる可能性があります。共有オブジェクト内へのシンボルの割り込みについては、「単純な解決」 で説明しています。この割り込みは、意図的に行うことができ、これを使用することにより、共有オブジェクトが提供する目的の機能を取り囲むことができます。また反対に、この割り込みは、同じ共通のシンボル名をアプリケーションと共有オブジェクトの両方に使用した結果として、知らないうちに実行される場合もあります。
共有オブジェクトを開発する場合は、シンボル bar と str の範囲をローカル結合に縮小して、このような事態から保護できます。次の例では、シンボル bar と str は、共有オブジェクトのインタフェースの一部としては利用できなくなっています。そのため、これらのシンボルは、外部のオブジェクトによって参照されることができないか、割り込みはできません。ユーザーは、インタフェースをこの共有オブジェクト用に効果的に定義できます。インプリメンテーションの基礎となる詳細を隠している間は、このインタフェースを管理できます。
$ 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] 0x00000548 0x00000018 FUNC LOCL H 0 .text bar [25] 0x000106a0 0x00000004 OBJT LOCL H 0 .data str [45] 0x00000508 0x0000002c FUNC GLOB D 0 .text foo
このようなシンボル範囲の縮小には、このほかにもパフォーマンスにおける利点があります。実行時に必要だったシンボル bar と str に対するシンボルの再配置は、現在は、関連する再配置に縮小されます。シンボル再配置のオーバーヘッドの詳細は、「再配置が実行されるとき」を参照してください。
リンク編集の間に処理されるシンボル数が多くなると、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] 0x00000570 0x00000018 FUNC LOCL H 0 .text bar [27] 0x000106d8 0x00000004 OBJT LOCL H 0 .data str [50] 0x00000530 0x0000002c FUNC GLOB D 0 .text foo
この例では、mapfile 指令の一部としてバージョン名 (libfoo.so.1.1) も定義しています。このバージョン名により、ファイルのシンボルインタフェースを定義する、内部バージョン定義が確立されます。バージョン定義はできるだけ作成してください。バージョン定義によって、ファイルの展開全体を通して使用できる、内部バージョンメカニズムの基礎が形成されます。第 9 章インタフェースおよびバージョン管理を参照してください。
注 - バージョン名が指定されていないと、出力ファイル名がバージョン定義のラベル付けに使用されます。出力ファイル内に作成されたバージョン情報は、リンカーの -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. No output written to libfoo.so.1
-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$' [28] 0x00000050 0x00000018 FUNC GLOB H 0 .text bar [29] 0x00000010 0x0000002c FUNC GLOB D 2 .text foo [30] 0x00000000 0x00000004 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] 0x00000508 0x00000018 FUNC LOCL H 0 .text bar [25] 0x00010644 0x00000004 OBJT LOCL H 0 .data str [42] 0x000004c8 0x0000002c 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] 0x00000050 0x00000018 FUNC LOCL H 0 .text bar [21] 0x00000000 0x00000004 OBJT LOCL H 0 .data str [30] 0x00000010 0x0000002c 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] 0x00010690 0x00000004 OBJT LOCL H 0 .data str [44] 0x000004e8 0x0000002c FUNC GLOB D 0 .text foo
-B eliminate オプションを使用して、コマンド行から自動削除指令「*」を表明することもできます。
作成するオブジェクトのシンボル参照が共有オブジェクト内の定義によって解決されると、そのシンボルは未定義のまま残ります。シンボルに対応する再配置情報が実行時の検索で使用されます。定義を提供する共有オブジェクトは、通常、1 つの依存条件になります。
実行時リンカーは、実行時にデフォルト検索モデルを使ってこの定義を見つけます。一般にオブジェクトは 1 つずつ検索されますが、その際、動的実行可能プログラムから、オブジェクトが読み込まれた順に各依存関係が処理されます 。
オブジェクトは、直接結合を使用するように作成することもできます。この方法では、シンボル参照と、シンボル定義を提供するオブジェクトとの関係は、作成されるオブジェクト内に維持されます。この情報を使えば、実行時リンカーは、参照先とシンボルを定義するオブジェクトを直接結合し、デフォルトのシンボル検索モデルをバイパスできます。第 6 章直接結合を参照してください。
リンカーは、重複したエントリと末尾部分文字列を削除することによって、文字列テーブルを圧縮します。この圧縮により、どのような文字列テーブルでもサイズが相当小さくなります。たとえば、.dynstr テーブルを圧縮すると、テキストセグメントが小さくなるため、実行時のページング作業が減ります。このような利点があるため、文字列テーブルの圧縮はデフォルトで有効に設定されています。
非常に多くのシンボルを提供するオブジェクトによって、文字列テーブルの圧縮のためにリンク編集時間が延びる可能性があります。開発時にこの負担を避けるには、リンカーの -z nocompstrtab オプションを使用してください。リンク編集時に行われる文字列テーブルの圧縮は、リンカーのデバッグトークン -D strtab,detail を使用して表示できます。