リンカーとライブラリ

シンボルの処理

入力ファイルの処理中に、入力再配置可能オブジェクトから局所シンボルが出力ファイルイメージに渡されます。大域シンボルはすべて、リンカーの内部に蓄積されます。この内部のシンボルテーブルは、処理される新しい大域シンボルのエントリごとに検索され、過去の入力ファイルで、同じ名前のシンボルに遭遇したことがあるかどうかが判別されます。同じ名前のシンボルに遭遇したことがある場合には、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 つのシンボルが削除された場合や、1 つのシンボルが他のシンボルよりも優先される場合に実行されます。このシンボル解析は、リンカーによって自動的に実行されます。たとえば、同じ結びつきを伴う複数のシンボルでは、あるファイルから未定義シンボルへのリファレンスは結合されるか、または他のファイルからの定義シンボルまたは未確定シンボル定義によって満たされます。あるいは、あるファイルからの未確定シンボル定義は、他のファイルからの定義シンボルの定義に結合されます。

解析を受けるシンボルは、大域結合またはウィーク結合されます。ウィーク結合の方が、大域結合よりも優先度が低くなります。そのため、異なる結びつきを伴うシンボルは、基本規則のわずかな変更に従って解析されます。しかし、まず初めに、シンボルがどのように作成されるかを紹介しましょう。

ウィークシンボルは通常、個別にあるいは大域シンボルの別名として、コンパイラによって定義されます。この機構では、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 と同じ属性が割り当てられていることに注意してください。この関係は、リンカーによって保持され、その結果、シンボルには出力イメージ内の同じ値が割り当てられます。

シンボル解析においては、ウィーク定義シンボルは、同じ名前の大域定義によって自動的に上書きされます。

単純なシンボル解析のこの他の形式は、再配置可能オブジェクトと共有オブジェクト間、または複数の共有オブジェクト間に発生し、挿入と呼ばれます。このような場合、シンボルが複数回定義されている場合、リンカーにより、再配置可能オブジェクト、または複数の共有オブジェクト間の最初の定義が自動的に採用されます。再配置可能オブジェクトの定義、または最初の共有オブジェクトの定義は、他のすべての定義上に挿入するといわれます。この挿入を使用すると、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 指示語を使用することにより、この予約名を定義できます。


#pragma weak read = _read

また、この予約名から、関数 read(2) の別名が生成されます。こうすることにより、ユーザーは、fread(3C) の実装に妥協することなく、自分専用の read() 関数を自由に定義できます。

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

リンカーの -m オプションを使用すると、挿入されるすべてのシンボルリファレンスのリストが、セクションの読み込みアドレス情報とともに標準出力に書き込まれます。

複雑な解析

複雑な解析は、同じ名前を持つ 2 つのシンボルが、異なる属性とともに検出された場合に発生します。この場合、リンカーは最も適切なシンボルを選択し、そのシンボル、対立する属性、およびそのシンボル定義を取り出したファイルの ID を示す警告メッセージを生成します。次に例を示します。


$ 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 つのファイルでは、サイズの必要条件が異なっています。シンボル配列の必要条件が異なる場合には、同様の診断プログラムが作成されます。この 2 つのケースの場合、リンカーの -t オプションを使用すると、診断プログラムを抑制できます。

この他の属性形式の違いに、シンボルの「タイプ」があります。次に例を示します。


$ 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

ここで、シンボル bar() は、データ項目と関数の両方として定義されています。


注 -

このコンテキスト内の型とは、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 オプションを使用すると、未定義シンボルが残っていた場合に、強制的に重大エラーにすることができます (「追加シンボルの定義」で説明している extern mapfile 指示文も参考にしてください)。

通常、自己組み込み共有オブジェクトは、外部シンボルへのすべてのリファレンスは名前の付いた依存関係によって満たされ、最大の柔軟性が提供がされます。この場合の共有オブジェクトは、共有オブジェクトの必要条件を満たす依存関係を判別し、確立したユーザー以外の、多数のユーザーによって使用されます。

ウィークシンボル

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

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

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

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


#pragma weak    foo

extern  void    foo(char *);

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

         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 ] [ extern ] ];
} [ dependency ];
name

このシンボル定義のセットのラベルは、もしあれば、イメージ内のバージョン定義を識別できます。詳細については、第 5 章「バージョンアップ」を参照してください。

scope

生成される出力ファイル内のシンボルのバインディングの可視性を示しています。これには、値 local (局所) または global (大域) のいずれかが入ります。mapfile で定義されたすべてのシンボルは、リンク編集プロセス中に、scope (スコープ) 内で global (大域) として処理されます。つまり、これらのシンボルは、入力ファイルのいずれかから入手された、同じ名前の他のシンボルに対して解析されます。ただし、local (局所) scope として定義シンボルは、生成される実行可能プログラムまたは共有オブジェクトのファイル内の局所結合が指定されたシンボルに変更されます。

symbol

要求されたシンボルの名前です。この名前のあとに、シンボル属性 (typevaluesize のいずれか) が付いていない場合には、シンボルリファレンスの作成になります。このリファレンスは、この項の最初に説明した -u オプションを使用して生成するリファレンスとまったく同じものです。このシンボル名にシンボル属性が付いている場合には、シンボル定義は、関連する属性を使用して生成されます。

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

type

シンボルのタイプ属性を示します。また、ここには、datafunctioncommon のいずれかが入ります。最初の 2 つのタイプ属性の結果は、絶対的なシンボル定義になります (「シンボルテーブル」を参照してください)。後者のタイプ属性の結果は、未確定シンボル定義になります。

value

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

size

シンボルのサイズ属性を示し、Snumber の書式をとります。

extern

シンボルが、作成されているオブジェクトに外部的に定義されていることを示します。このオプションを使用して、-z defs オプションで示された未定義シンボル (「共有オブジェクトの生成」を参照) を抑制できます。

dependency

この定義が継承する version definition (バージョン定義) を示します。詳細については、第 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 の範囲を局所結合に縮小して、このような事態から保護することができます。次に例を示します。


$ 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 は、共有オブジェクトのインタフェースの一部としては使用できません。そのため、これらのシンボルは、外部のオブジェクトによって参照されることができないか、割り込みはできません。ユーザーは、インタフェースをこの共有オブジェクト用に効果的に定義できます。インプリメンテーションの基礎となる詳細を隠している間は、このインタフェースを管理できます。

このようなシンボル範囲の縮小には、この他にもパフォーマンスにおける利点があります。実行時に必要だったシンボル 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

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


$ 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 からデータを処理するのと同じ方法で、再配置可能オブジェクト内に組み込まれたシンボル縮小を読み取り、解釈します。

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


$ cc -o lib.so.1 -M mapfile -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