リンカーとライブラリ

シンボル範囲の縮小

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