リンカーとライブラリ

シンボル範囲の縮小

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
$ nm -x libfoo.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

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

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

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

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


$ cat mapfile
{
        local:
                bar;
                str;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ nm -x libfoo.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
ISV_1.1 {
        global:
                foo;
        local:
                *;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ nm -x libfoo.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

この例では、バージョン名 libfoo.so.1.1mapfile 指令の一部として定義しています。このバージョン名により、ファイルのシンボルインタフェースを定義する、内部バージョン定義が確立されます。バージョン定義はできるだけ作成してください。バージョン定義によって、ファイルの展開全体を通して使用できる、内部バージョンメカニズムの基礎が形成されます。第 5 章アプリケーションバイナリインタフェースとバージョン管理を参照してください。


注 –

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


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


$ cat mapfile
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
ISV_1.1 {
        global:
                foo;
        local:
                *;
};
$ ld -o libfoo.o -M mapfile -r foo.o bar.o
$ nm -x libfoo.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 libfoo.so.1 -G libfoo.o
$ nm -x libfoo.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 libfoo.o -M mapfile -B reduce -r foo.o bar.o
$ nm -x libfoo.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

シンボル削除

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

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


$ cat mapfile
ISV_1.1 {
        global:
                foo;
        local:
                str;
        eliminate:
                *;
};
$ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c
$ nm -x libfoo.so.1 | egrep "foo$|bar$|str$"
[31]    |0x00010428|0x00000004|OBJT |LOCL |0x0  |12     |str
[35]    |0x00000348|0x00000028|FUNC |GLOB |0x0  |6      |foo

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