リンカーとライブラリ

フィルタとしての共有オブジェクト

フィルタとは、代替共有オブジェクトへの間接参照を提供するために使用される特殊な形式の共有オブジェクトのことをいいます。2 つの形式の共有オブジェクトフィルタがあります。

標準フィルタは、基本的に単一のシンボルテーブルからなり、実行時環境からコンパイル環境を抽象化するメカニズムを提供します。このフィルタを使用するリンク編集は、フィルタ自体によって提供されるシンボルを参照しますが、シンボル参照の解釈は、実行時に代替ソースから提供されます。

標準フィルタは、リンカーの -F フラグによって識別されます。このフラグは、実行時にシンボル参照を与える共有オブジェクトを示す関連ファイル名をとります。この共有オブジェクトは、フィルタ対象と呼ばれます。 -F フラグを複数回使用すると、複数のフィルタ対象を記録できます。

フィルタ対象を実行時理できないか、またはフィルタによって定義されたシンボルがフィルタ対象内に見つからない場合、そのフィルタは無視されて、シンボル解決は次に関連する依存関係に続けられます。

補助フィルタも同様のメカニズムを備えていますが、フィルタ自体にそのシンボルに対応する実装が含まれます。 フィルタを使用するリンク編集ではフィルタ自体によって提供されたシンボルを参照しますが、シンボル参照の実装は、実行時に代替ソースから提供できます。

補助フィルタは、リンカーの -f フラグを使用して識別されます。このフラグは、実行時にシンボルを与えるために使用できる共有オブジェクトを示す関連ファイル名をとります。この共ブジェクトは、フィルタ対象と呼ばれます。 -f フラグを複数回使用すると、複数のフィルタ対象を記録できます。

フィルタ対象を実行時に処理できないか、またはフィルタ対象内にフィルタが見つからないと、フィルタ内のシンボルの実装が使用されます。

標準フィルタの生成

まずフィルタ対象 libbar.so.1 を定義し、それに対してこのフィルタ手法を適用します。このフィルタ対象は、いくつかの再配置可能オブジェクトから構築される場合があります。これらのオブジェクトの 1 つは、ファイル bar.c から発生し、シンボル foobar を与えます。


$ cat bar.c
char * bar = "bar";

char * foo()
{
    return("defined in bar.c");
}
$ cc -o libbar.so.1 -G -K pic .... bar.c ....

標準フィルタ libfoo.so.1 は、シンボル foobar に対して生成されて、フィルタ対象 libbar.so.1 への関連付けを示します。次に例を示します。


$ cat foo.c
char * bar = 0;

char * foo(){}

$ LD_OPTIONS='-F libbar.so.1' ¥
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ ln -s libfoo.so.1 libfoo.so
$ dump -Lv libfoo.so.1 | egrep "SONAME|FILTER"
[1]     SONAME   libfoo.so.1
[2]     FILTER   libbar.so.1

注 -

ここで、環境変数 LD_OPTIONS は、このコンパイラドライバが -F オプションをそれ自体のオプションの 1 つとして解釈しないようにするために使用されています。


リンカーは、標準フィルタ libfoo.so.1 を参照して動的実行可能ファイルまたは共有オブジェクトを構築する場合、シンボル解決中にフィルタシンボルテーブルからの情報を使用します (詳細については、「シンボル解析」を参照してください)。

実行時に、フィルタのシンボルを参照すると、必ずフィルタ対象 libbar.so.1 がさらに読み込まれます。実行時リンカーは、このフィルタ対象を使用して、libfoo.so.1 によって定義されたシンボルを解釈処理します。

たとえば、次の動的実行可能ファイル prog は、シンボル foobar を参照します。これらは、リンク編集中にフィルタ libfoo.so.1 から解釈処理されます。


$ cat main.c
extern char * bar, * foo();

main(){
    (void) printf("foo() is %s: bar=%s¥n", foo(), bar);
}
$ cc -o prog main.c -R. -L. -lfoo
$ prog
foo() is defined in bar.c: bar=bar

動的実行可能ファイル prog を実行すると、関数 foo() とデータ項目 bar が、フィルタ libfoo.so.1 からではなく、フィルタ対象 libbar.so.1 から取得されます。


注 -

この例では、フィルタ対象 libbar.so.1 がフィルタ libfoo.so.1 に一意に関連付けられています。このため、prog を実行した結果読み込まれる可能性がある他のオブジェクトからのシンボル参照を満たすために使用することができません。


標準フィルタは、既存の共有オブジェクトのサブセットインタフェース、または多数の既存の共有オブジェクトに及ぶインタフェースグループを定義するためのメカニズムとなります。Solaris で使用されるフィルタには、/usr/lib/libsys.so.1/usr/lib/libdl.so.1 の 2 つがあります。

最初のフィルタは、標準 C ライブラリ /usr/lib/libc.so.1 のサブセットになります。このサブセットは、準拠するアプリケーションによってインポートしなければならない C ライブラリ内の ABI に準拠する関数とデータ項目を表わします。

2 つめのフィルタは、実行時リンカー自体へのユーザーインタフェースを定義します。このインタフェースは、コンパイル環境で (libdl.so.1 から) 参照されるシンボルと、実行時環境内で (ld.so.1 から) 作成される実際の実装結合間の抽象化を提供します。

複数のフィルタ対象を使用するフィルタの一例として、/usr/lib/libxnet.so.1 があります。このライブラリは、/usr/lib/libsocket.so.1/usr/lib/libnsl.so.1、および /usr/lib/libc.so.1 から、ソケットと XTI インタフェースを提供します。

標準フィルタ内のコードは実行時に参照されないため、フィルタ内に定義された関数に内容を加えても意味がありません。フィルタコードが再配置を必要とする場合がありますが、実行時にそのフィルタを処理すると不要なオーバーヘッドが生じます。関数は空のルーチンとして定義するか、直接 mapfile から定義してください(「追加シンボルの定義」を参照)。

フィルタ内にデータシンボルを生成するときにも注意が必要です。データ項目は必ず初期設定して、動的実行可能ファイルから参照されるように保証する必要があります。

リンカーによって実行されるより複雑なシンボル解釈処理の中には、シンボルサイズを含むシンボルの属性に関する知識を必要とするものがあります (詳細については、「シンボル解析」を参照)。このため、フィルタ内のシンボルの属性がフィルタ対象内のシンボルの属性と一致するようにシンボルを生成することをお勧めします。これにより、リンク編集処理では、実行時に使用されるシンボル定義と互換性のある方法でフィルタが解析されます。

補助フィルタの生成

補助フィルタの作成方法は、標準フィルタの場合と基本的に同じです (詳細については、「標準フィルタの生成」を参照)。まず、このフィルタ手法を適用するフィルタ対象 libbar.so.1 を定義します。このフィルタ対象は、いくつかの再配置可能オブジェクトから構築される場合があります。これらのオブジェクトの 1 つは、ファイル bar.c から発生し、シンボル foo を提供します。


$ cat bar.c
char * foo()
{
    return("defined in bar.c");
}
$ cc -o libbar.so.1 -G -K pic .... bar.c ....

補助フィルタ libfoo.so.1 が、シンボル foobar に対して生成されて、フィルタ対象 libbar.so.1 への関連付けを示します。次に例を示します。


$ cat foo.c
char * bar = "foo";

char * foo()
{
    return ("defined in foo.c");
}
$ LD_OPTIONS='-f libbar.so.1' ¥
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ ln -s libfoo.so.1 libfoo.so
$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"
[1]     SONAME    libfoo.so.1
[2]     AUXILIARY libbar.so.1

注 -

ここで、環境変数 LD_OPTIONS は、このコンパイラドライバが -f オプションをそれ自体のオプションの 1 つとして解釈しないようにするために使用されています。


リンカーは、補助フィルタ libfoo.so.1 を参照して動的実行可能ファイルまたは共有オブジェクトを構築する場合、シンボル解決中、フィルタシンボルテーブルの情報を使用します (詳細については、「シンボル解析」を参照)。

実行時にフィルタのシンボルを参照すると、フィルタ対象 libbar.so.1 が検索されます。このフィルタ対象が見つかると、実行時リンカーは、このフィルタ対象を使用して、libfoo.so.1 によって定義されたすべてのシンボルを解釈処理します。このフィルタ対象が見つからないか、またはフィルタ対象にフィルタからのシンボルがない場合は、フィルタ内のシンボルの元の値が使用されます。

たとえば、次の動的実行可能ファイル prog は、シンボル foobar を参照します。これらのシンボルは、フィルタ libfoo.so.1 からのリンク編集中に解釈処理されます。


$ cat main.c
extern char * bar, * foo();

main(){
    (void) printf("foo() is %s: bar=%s¥n", foo(), bar);
}
$ cc -o prog main.c -R. -L. -lfoo
$ prog
foo() is defined in bar.c: bar=foo

動的実行可能ファイル prog を実行すると、関数 foo() は、フィルタ libfoo.so.1 からではなく、フィルタ対象 libbar.so.1 から取得されます。ただし、データ項目 bar は、フィルタ libfoo.so.1 から取得されます。このシンボルは、フィルタ対象 libbar.so.1 に代替定義を持たないためです。

補助フィルタは、既存の共有オブジェクトの代替インタフェースを定義するメカニズムとなります。このメカニズムは Solaris で使用されて、プラットフォーム固有の共有オブジェクト内に最適化された機能を提供します。例は、「命令セット固有の共有オブジェクト」および 「プラットフォーム固有の共有オブジェクト」を参照してください。

フィルタ対象の処理

実行時リンカーによるフィルタ処理は、フィルタ内のシンボルへの参照が生じるまで、フィルタ対象の読み込みを延期します。この実装は、各フィルタ対象に対して必要に応じて dlopen(3DL) を実行するフィルタに似ています。この実装は、ldd(1) などのツールによって生じる可能性がある、依存関係の報告における違いの原因となるものです。

フィルタを作成して、そのフィルタ対象を実行時に即時処理する場合は、リンカーの -z loadfltr オプションを使用できます。また、プロセス内のフィルタすべての即時処理は、どの値にも環境変数 LD_LOADFLTR を設定することによってトリガーすることもできます。