共有オブジェクトは、フィルタとして機能するように定義できます。この手法には、フィルタが提供するインタフェースと、代替共有オブジェクトとの関連付けが含まれます。代替共有オブジェクトは実行時に、「フィルタ」により提供される 1 つまたは複数のインタフェースを供給します。この代替共有オブジェクトは 「フィルティー」と呼ばれます。「フィルティー」は、共有オブジェクトと同じように構築されます。
フィルタ処理は、実行時環境からコンパイル環境を抽象化するメカニズムを提供します。リンク編集時には、フィルタインタフェースに結合するシンボル参照は、フィルタシンボル定義に解決されます。実行時には、フィルタインタフェースに結合するシンボル参照は代替共有オブジェクトにリダイレクトできます。
共有オブジェクト内で定義される各インタフェースは、mapfile FILTER または AUXILIARY 属性を SYMBOL_SCOPE および SYMBOL_VERSION ディレクティブに使用することで、フィルタとして定義できます。また、共有オブジェクトはリンカーの mapfile FILTER ディレクティブ、あるいは –F または –f コマンド行オプションを使用して、共有オブジェクトが提供するすべてのインタフェースをフィルタとして定義することもできます。これらの手法は、一般に個別に使用されますが、同じ共有オブジェクトの中で組み合わせることもできます。
フィルタ処理には、3 つの形があります。
このフィルタ処理で必要となるのは、フィルタ処理対象のインタフェースのシンボルテーブルエントリだけです。実行時には、「フィルティー」からフィルタシンボル定義の実装を提供する必要があります。
リンカーの mapfile FILTER ディレクティブまたは FILTER シンボル別属性、あるいはリンカーの –F オプションを使用することで、インタフェースは標準フィルタとして機能するように定義されます。この mapfile キーワードまたはコマンド行オプションは、実行時にシンボル定義を提供する必要がある 1 つ以上のフィルティーの名前で修飾されます。
実行時に処理できない「フィルティー」はスキップされます。「フィルティー」内に標準フィルタシンボルが見つからない場合も、「フィルティー」はスキップされます。どちらの場合も、フィルタにより提供されるシンボル定義は、このシンボル検索を満たすためには使用されません。
弱いフィルタ処理は、標準フィルタ処理の一種です。実行時に、弱いフィルタは標準フィルタと同じように処理されます。弱いフィルタと標準フィルタでは、–z discard-unused=dependencies オプションで未使用の依存関係の処理が有効になっている場合にリンカーが弱いフィルタの依存関係を処理する方法が異なります。
通常、リンカーはライブラリの外部シンボルを、シンボル定義を提供するコマンド行の最初のライブラリに解決します。弱いフィルタの場合、同じシンボルを提供するフィルティーもコマンド行に存在すると、このようなシンボルは無視されます。このようなシンボルをフィルティーに直接解決すると、未使用の依存関係の処理によってフィルタを依存関係として削除でき、よりシンプルで効率的なオブジェクトを実現できます。
リンカーの mapfile FILTER ディレクティブ、または FILTER シンボル別属性を使用することで、インタフェースは弱いフィルタとして機能するように定義されます。この mapfile キーワードは、実行時にシンボル定義を提供する必要がある 1 つ以上のフィルティーの名前で修飾されます。
このフィルタ処理は標準フィルタ処理と類似したメカニズムを提供しますが、補助フィルタインタフェースに対応するフォールバック実装がフィルタに含まれる点が異なります。実行時には、「フィルティー」からシンボル定義の実装を提供できます。
リンカーの mapfile FILTER ディレクティブや FILTER または AUXILIARY シンボル別属性を使用したり、リンカーの –f オプションを使用したりすると、インタフェースは補助フィルタとして機能するように定義されます。この mapfile キーワードまたはオプションは、実行時にシンボル定義を提供できる 1 つ以上のフィルティーの名前で修飾されます。
実行時に処理できない「フィルティー」はスキップされます。「フィルティー」内に補助フィルタシンボルが見つからない場合も、「フィルティー」はスキップされます。どちらの場合も、フィルタにより提供されるシンボル定義は、このシンボル検索を満たすために使用されます。
標準フィルタを生成するには、まずフィルタ処理を適用する「フィルティー」を定義する必要があります。次の例では、シンボル foo と bar を提供する「フィルティー」filtee.so.1 を構築します。
$ cat filtee.c char *bar = "defined in filtee"; char *foo() { return("defined in filtee"); } $ cc -o filtee.so.1 -G -K pic filtee.c
標準フィルタは、オブジェクトレベルで、または個々のシンボルに対して定義できます。共有オブジェクトによって提供されるすべてのインタフェースをフィルタとして宣言するには、リンカーの mapfile FILTER ディレクティブ、または –F コマンド行オプションを使用します。共有オブジェクトの個々のインタフェースをフィルタとして宣言するには、リンカーの mapfile と FILTER シンボル属性を使用します。
次の例では、共有オブジェクト filter.so.1 がフィルタとして定義されています。filter.so.1 はシンボル foo と bar を提供し、それ自体が「フィルティー」filtee.so.1 のフィルタです。この例では、コンパイラドライバが –F オプションを解釈しないように、環境変数 LD_OPTIONS が使用されています。
$ cat filter.c #include <stdio.h> char *bar = NULL; char *foo() { return (NULL); } $ LD_OPTIONS='-F filtee.so.1' \ cc -o filter.so.1 -G -K pic -h filter.so.1 -R. filter.c $ elfdump -d filter.so.1 | egrep "SONAME|FILTER" [2] SONAME 0xee filter.so.1 [3] FILTER 0xfb filtee.so.1
mapfile は –F コマンド行オプションの代わりに使用できます。
$ cat mapfile $mapfile_version 2 FILTER { FILTEE = filtee.so.1; TYPE = STANDARD; }; $ cc -o filter.so.1 -G -K pic -h filter.so.1 -M mapfile -R. filter.c $ elfdump -d filter.so.1 | egrep "SONAME|FILTER" [2] SONAME 0xee filter.so.1 [3] FILTER 0xfb filtee.so.1
動的実行可能ファイルまたは共有オブジェクトを作成する場合、リンカーは標準フィルタ filter.so.1 を依存関係として参照できます。リンカーは、フィルタのシンボルテーブルの情報を使用してシンボル解決を行います。しかし、実行時にフィルタのシンボルを参照すると、必ず「フィルティー」filtee.so.1 がさらにロードされます。実行時リンカーはこの「フィルティー」を使用して、filter.so.1 によって定義されたシンボルを解決します。この「フィルティー」が見つからないか、あるいは「フィルティー」内にフィルタシンボルが見つからない場合は、このシンボル検索でそのフィルタはスキップされます。
たとえば、次の動的実行可能ファイル prog は、シンボル foo と bar を参照します。これらのシンボルは、フィルタ filter.so.1 からのリンク編集中に解決されます。prog を実行すると、foo と bar が、フィルタ filter.so.1 からではなく、「フィルティー」filtee.so.1 から取得されます。
$ cat main.c extern char *bar, *foo(); void main() { (void) printf("foo is %s: bar is %s\n", foo(), bar); } $ cc -o prog main.c -R. filter.so.1 $ prog foo is defined in filtee: bar is defined in filtee
次の例では、共有オブジェクト filter.so.2 は、インタフェースの 1 つである foo を フィルティー filtee.so.1 上のフィルタとして定義します。
$ cat filter.c char *bar = "defined in filter"; $ cat mapfile $mapfile_version 2 SYMBOL_SCOPE { global: foo { TYPE=FUNCTION; FILTER=filtee.so.1 }; }; $ cc -o filter.so.2 -G -K pic -h filter.so.2 -M mapfile -R. filter.c $ elfdump -d filter.so.2 | egrep "SONAME|FILTER" [2] SONAME 0xd8 filter.so.2 [3] SUNW_FILTER 0xfb filtee.so.1 $ elfdump -y filter.so.2 | egrep "foo|bar" [1] F [3] filtee.so.1 foo [10] D <self> bar
実行時にフィルタのシンボル foo を参照すると、必ず「フィルティー」filtee.so.1 がさらに読み込まれます。実行時リンカーは、「フィルティー」を使用して、filter.so.2 が定義したシンボル foo だけを解決します。シンボル bar への参照は、 filter.so.2 からのシンボルを常に使用し、このシンボルに対して「フィルティー」処理は定義されません。
たとえば、次の動的実行可能ファイル prog は、フィルタ filter.so.2 からのリンク編集中に解決されるシンボル foo と bar を参照します。prog の実行により、foo が「フィルティー」filtee.so.1 から取得され、bar がフィルタ filter.so.2 から取得されます。
$ cc -o prog main.c -R. filter.so.2 $ prog foo is defined in filtee: bar is defined in filter
これらの例では、「フィルティー」filtee.so.1 がフィルタに一意に関連付けられています。このため、prog を実行した結果読み込まれる可能性があるほかのオブジェクトからのシンボル参照を満たすために、「フィルティー」を使用することができません。
標準フィルタは、既存の共有オブジェクトのサブセットインタフェースを定義するための便利なメカニズムを提供します。標準フィルタは、多数の既存の共有オブジェクトに及ぶインタフェースグループを作成します。標準フィルタはまた、インタフェースをその実装にリダイレクトする手段も提供します。いくつかの標準フィルタが、Oracle Solaris OS で使用されています。
/lib/libxnet.so.1 フィルタは、複数の「フィルティー」を使用します。このライブラリは、/lib/libsocket.so.1、 /lib/libnsl.so.1、および /lib/libc.so.1 から、ソケットと XTI インタフェースを提供します。
libc.so.1 は、実行時リンカーへのインタフェースフィルタを定義します。これらのインタフェースは、libc.so.1 のコンパイル環境で参照されるシンボルとld.so.1(1)の実行時環境内で作り出される実際の実装結合間の抽象化を提供します。
libnsl.so.1 は、標準フィルタgethostname(3C)を libc.so.1 に対して定義します。以前は、libnsl.so.1 も libc.so.1 もこのシンボルの同じ実装を提供していました。libnsl.so.1 をフィルタとして設定することで、gethostname() の実装は 1 つだけ必要となります。libnsl.so.1 は継続して gethostname() をエクスポートするため、このライブラリインタフェースも以前のリリースと互換性があります。
標準フィルタ内のコードは実行時に参照されることはないため、フィルタとして定義された関数に内容を追加しても意味がありません。どのようなフィルタコードでも再配置を必要とする場合があり、実行時にそのフィルタを処理すると不要なオーバーヘッドが生じます。関数は空のルーチンとして定義するか、直接 mapfile から定義してください。SYMBOL_SCOPE および SYMBOL_VERSION ディレクティブを参照してください。
フィルタ内にデータシンボルを生成するときは、常にデータをセクションに関連付けてください。この関連付けは、再配置可能なオブジェクトファイル内にシンボルを定義することで行うことができます。この関連付けは、mapfile 内でシンボルを size 宣言あり、 value 宣言なしで定義しても行うことができます。SYMBOL_SCOPE および SYMBOL_VERSION ディレクティブを参照してください。このようにデータを定義することで、動的実行可能ファイルからの参照が正しく確立されます。
リンカーによって実行される、より複雑なシンボル解決の中には、シンボルサイズを含むシンボルの属性に関する知識を必要とするものがあります。このため、フィルタ内のシンボルの属性が「フィルティー」内のシンボルの属性と一致するようにシンボルを生成する必要があります。属性の一貫性を維持することで、リンク編集処理では、実行時に使用されるシンボル定義と互換性のある方法でフィルタが解析されます。シンボル解決を参照してください。
標準フィルタは、ベースとなるシステムを進化させながら実行時の互換性を維持するという問題に、シンプルかつ効果的な解決策を提供します。ただし、フィルタには継続的なオーバーヘッドも伴います。これらのフィルタは機能は提供しませんが、プログラムを実行するたびにシステムによってロードおよび処理する必要があります。通常は、まだ再構築されていない古いコードのために、プログラムによるこれらのフィルタへのリンクを停止することをお勧めします。
可能な場合は、オブジェクトを構築する際に不要なフィルタを削除することが最善の解決策です。ただし、これは実行が難しい場合があります。特に、オープンソースパッケージの多くは複数のバージョンのオペレーティングシステムで機能することを意図した複雑な構成システムを備えています。バージョンによってはライブラリが必要なコンテンツを提供する場合もありますが、新しいバージョンではこれらは単なるフィルタです。これらの複雑さに取り組む代わりに、構成はすべてにリンクするだけです。Oracle Solaris では、これが libpthread などのライブラリで頻繁に起こります。
弱いフィルタを –z discard-unused=dependencies コマンド行オプションと組み合わせて使用することで、この問題に自動化された解決策を提供します。通常、シンボル定義を提供するコマンド行の最初のライブラリからシンボルを取得することで、リンカーはライブラリの外部シンボルを解決します。弱いフィルタの場合、同じフィルターを提供するフィルティーもコマンド行に存在すると、このようなシンボルは無視されます。このようなシンボルをフィルティーに直接解決すると、未使用の依存関係の処理によってフィルタを削除できます。これにより、アップストリームのコードベースを大きく変更することなく、よりシンプルで効率的なオブジェクトを実現できます。
次の例では、libc から標準 printf() を提供する、libprint という名前のフィルタオブジェクトを使用しています。
$ cat mapfile-libprint-std $mapfile_version 2 FILTER { FILTEE = "libc.so.1"; TYPE = STANDARD; }; SYMBOL_SCOPE { global: printf { TYPE = FUNCTION }; }; $ ld -o libprint.so.1 -G -h libprint.so.1 -Mmapfile-libprint-std $ elfdump libprint.so.1 | egrep 'SONAME|FILTER' [0] SONAME 0x1 libprint.so.1 [1] FILTER 0x4c libc.so.1
libprint に対して構築されたアプリケーションでは、実行時に使用される printf() 関数がフィルティー libc に含まれていても、このフィルタが実行時にロードされる必要があります。
$ cc hello.c -o hello -L. -R. libprint.so.1 $ elfdump -d hello | grep NEEDED [0] NEEDED 0x13b libprint.so.1 [1] NEEDED 0x125 libc.so.1 $ ./hello hello, world
libprint フィルタは弱いフィルタとして再構築されます。Hello World プログラムは以前と同様に libprint にリンクしますが、未使用の依存関係の処理は有効になります。結果として生じるプログラムが、libc から直接 printf() を解決します。これにより、リンカーは未使用の libprint の依存関係を破棄できます。
$ cat mapfile-libprint-weak $mapfile_version 2 FILTER { FILTEE = "libc.so.1"; TYPE = WEAK; }; SYMBOL_SCOPE { global: printf { TYPE = FUNCTION }; }; $ ld -o libprint.so.1 -G -h libprint.so.1 -Mmapfile-libprint-weak $ elfdump libprint.so.1 | egrep 'SONAME|FILTER' [0] SONAME 0x1 libprint.so.1 [1] FILTER 0x4c libc.so.1 [14] FLAGS_1 0x20000000 [ WEAKFILTER ] $ cc hello.c -o hello -L. -R. libprint.so.1 -zdiscard-unused=dependencies $ elfdump -d hello | grep NEEDED [0] NEEDED 0x125 libc.so.1 $ ./hello hello, world
最新バージョンの Oracle Solaris では、–z discard-unused=dependencies オプションで未使用の依存関係の処理が有効になっている場合に、不要なフィルタの依存関係を自動的に削除できるように、libpthread などのライブラリが標準フィルタではなく弱いフィルタとして構築されます。
補助フィルタを生成するには、まずフィルタ処理を適用する「フィルティー」を定義する必要があります。次の例では、シンボル foo を提供する「フィルティー」filtee.so.1 を構築します。
$ cat filtee.c char *foo() { return("defined in filtee"); } $ cc -o filtee.so.1 -G -K pic filtee.c
補助フィルタ処理は、オブジェクトレベルで、または個々のシンボルに対して定義できます。共有オブジェクトによって提供されるすべてのインタフェースを補助フィルタとして宣言するには、リンカーの mapfile FILTER ディレクティブ、または –f コマンド行オプションを使用します。共有オブジェクトの個々のインタフェースを補助フィルタとして宣言するには、リンカーの mapfile FILTER ディレクティブ、または –f コマンド行オプションを使用します。共有オブジェクトの個々のインタフェースを補助フィルタとして宣言するには、リンカーの mapfile と AUXILIARY または FILTER シンボル属性を使用します。
次の例では、共有オブジェクト filter.so.1 が補助フィルタとして定義されています。filter.so.1 はシンボル foo と bar を提供し、それ自体が「フィルティー」filtee.so.1 の補助フィルタです。この例では、コンパイラドライバが –f オプションを解釈しないように、環境変数 LD_OPTIONS が使用されています。
$ cat filter.c char *bar = "defined in filter"; char *foo() { return ("defined in filter"); } $ LD_OPTIONS='-f filtee.so.1' \ cc -o filter.so.1 -G -K pic -h filter.so.1 -R. filter.c $ elfdump -d filter.so.1 | egrep "SONAME|AUXILIARY" [2] SONAME 0xee filter.so.1 [3] AUXILIARY 0xfb filtee.so.1
mapfile は –f コマンド行オプションの代わりに使用できます。
$ cat mapfile $mapfile_version 2 FILTER { FILTEE = filtee.so.1; TYPE = AUXILIARY; }; $ cc -o filter.so.1 -G -K pic -h filter.so.1 -M mapfile -R. filter.c $ elfdump -d filter.so.1 | egrep "SONAME|AUXILIARY" [2] SONAME 0xee filter.so.1 [3] AUXILIARY 0xfb filtee.so.1
動的実行可能ファイルまたは共有オブジェクトを作成する場合、リンカーは補助フィルタ filter.so.1 を依存関係として参照できます。リンカーは、フィルタのシンボルテーブルの情報を使用してシンボル解決を行います。しかし、実行時にフィルタのシンボルを参照すると、「フィルティー」filtee.so.1 が検索されます。この「フィルティー」が見つかると、実行時リンカーは、この「フィルティー」を使用して、filter.so.1 によって定義されたすべてのシンボルを解決します。この「フィルティー」が見つからないか、あるいは「フィルティー」内にフィルタからのシンボルが見つからない場合は、フィルタ内の元のシンボルが使用されます。
たとえば、次の動的実行可能ファイル prog は、シンボル foo と bar を参照します。これらのシンボルは、フィルタ filter.so.1 からのリンク編集中に解決されます。prog を実行すると、foo が、フィルタ filter.so.1 からではなく、「フィルティー」filtee.so.1 から取得されます。しかし、bar はフィルタ filter.so.1 から取得されます。これは、「フィルティー」filtee.so.1 内にこのシンボルの代替定義が存在しないためです。
$ cat main.c extern char *bar, *foo(); void main() { (void) printf("foo is %s: bar is %s\n", foo(), bar); } $ cc -o prog main.c -R. filter.so.1 $ prog foo is defined in filtee: bar is defined in filter
次の例では、共有オブジェクト filter.so.2 は、インタフェース foo を「フィルティー」filtee.so.1 上の補助フィルタとして定義します。
$ cat filter.c char *bar = "defined in filter"; char *foo() { return ("defined in filter"); } $ cat mapfile $mapfile_version 2 SYMBOL_SCOPE { global: foo { AUXILIARY=filtee.so.1 }; }; $ cc -o filter.so.2 -G -K pic -h filter.so.2 -M mapfile -R. filter.c $ elfdump -d filter.so.2 | egrep "SONAME|AUXILIARY" [2] SONAME 0xd8 filter.so.2 [3] SUNW_AUXILIARY 0xfb filtee.so.1 $ elfdump -y filter.so.2 | egrep "foo|bar" [1] A [3] filtee.so.1 foo [10] D <self> bar
実行時にフィルタのシンボル foo を参照すると、必ず「フィルティー」filtee.so.1 が検索されます。「フィルティー」が見つかると、「フィルティー」が読み込まれます。「フィルティー」は filter.so.2 によって定義されたシンボル foo の解決に使用されます。「フィルティー」が検索されなかった場合、filter.so.2 によって定義されたシンボル foo が使用されます。シンボル bar への参照は、 filter.so.2 からのシンボルを常に使用し、このシンボルに対して「フィルティー」処理は定義されません。
たとえば、次の動的実行可能ファイル prog は、フィルタ filter.so.2 からのリンク編集中に解決されるシンボル foo と bar を参照します。「フィルティー」 filtee.so.1 が存在する場合、prog の実行により foo が「フィルティー」 filtee.so.1 から、bar がフィルタ filter.so.2 から取得されます。
$ cc -o prog main.c -R. filter.so.2 $ prog foo is defined in filtee: bar is defined in filter
「フィルティー」 filtee.so.1が存在しない場合、prog を実行すると、foo と bar がフィルタ filter.so.2 から取得されます。
$ prog foo is defined in filter: bar is defined in filter
これらの例では、「フィルティー」filtee.so.1 がフィルタに一意に関連付けられています。このため、prog を実行した結果読み込まれる可能性があるほかのオブジェクトからのシンボル参照を満たすために、「フィルティー」を使用することができません。
補助フィルタは、既存の共有オブジェクトの代替インタフェースを定義するメカニズムとなります。このメカニズムは Oracle Solaris OS で使用され、ハードウェア機能での最適な機能およびプラットフォーム固有の共有オブジェクトを提供します。例については、機能固有の共有オブジェクトおよびシステム固有の共有オブジェクトを参照してください。
標準フィルタを定義している各インタフェースと、補助フィルタを定義している各インタフェースは、同じ共有オブジェクト内に定義できます。こうしたフィルタ定義の組み合わせを実現するには、mapfile のキーワードである FILTER と AUXILIARY を使って、必要な「フィルティー」を割り当てます。
–F または –f オプション、あるいは mapfile FILTER ディレクティブを使用して自身のインタフェースのすべてをフィルタとして定義する共有オブジェクトは、標準弱フィルタか補助フィルタのどちらかです。
共有オブジェクトでは、個々のインタフェースをフィルタとして機能するように定義するとともに、そのオブジェクトのすべてのインタフェースをフィルタとして機能するように定義することができます。その場合、特定のインタフェースに対して定義された個別フィルタ処理が、まず処理されます。個別インタフェースフィルタに対するフィルティーを確立できなかった場合は、フィルタのすべてのインタフェースに対して定義されたフィルティーが必要に応じてフォールバックを提供します。
たとえば、フィルタ filter.so.1 があるとします。このフィルタでは、すべてのインタフェースがフィルティー filtee.so.1 に対する補助フィルタとして機能するように、リンカーの –f オプションを使って定義されています。さらに filter.so.1 では、個別インタフェース foo がフィルティー foo.so.1 に対する標準フィルタとなるように、mapfile のシンボル属性 FILTER を使って定義されています。さらに filter.so.1 では、個別インタフェース bar がフィルティー bar.so.1 に対する補助フィルタとなるように、mapfile のシンボル属性 AUXILIARY を使って定義されています。
foo への外部参照が発生すると、「フィルティー」foo.so.1 が処理されます。foo が foo.so.1 で見つからなかった場合、このフィルタに対する処理はそれ以上実行されません。この場合にフォールバック処理が実行されない理由は、foo が標準フィルタとして定義されているからです。
bar への外部参照が発生すると、「フィルティー」bar.so.1 が処理されます。bar が bar.so.1 で見つからなかった場合、「フィルティー」filtee.so.1 によるフォールバック処理が実行されます。この場合にフォールバック処理が実行される理由は、bar が補助フィルタとして定義されているからです。bar が filtee.so.1 で見つからなかった場合、最終的にはフィルタ filter.so.1 内の bar の定義に基づいて外部参照が解決されます。
実行時リンカーによるフィルタ処理は、フィルタ内のシンボルが参照されるまで、フィルティーの読み込みを遅延します。この実装は、必要に応じてモード RTLD_LOCAL を使用して各「フィルティー」に対して dlopen(3C) を実行するフィルタに似ています。この実装は、ldd(1) などのツールによって作成される、依存関係の報告における違いの原因となるものです。
実行時に「フィルティー」の即時処理を起動するフィルタを作成する場合には、リンカーの –z loadfltr オプションを使用できます。さらに、LD_LOADFLTR 環境変数を任意の値に設定することで、プロセス内のすべての「フィルティー」の即時処理を開始できます。