リンカーとライブラリ

バージョン定義への結合

動的実行可能ファイルまたは共有オブジェクトが、他の共有オブジェクトに対して構築される場合、これらの依存関係は結果オブジェクトに記録されます (詳細は、「共有オブジェクトの処理」「共有オブジェクト名の記録」を参照)。これらの共有オブジェクトの依存関係にバージョン定義も含まれる場合、関連のバージョン依存関係は結果オブジェクトに記録されます。

次の例は、前述のデータファイルを取り上げて、コンパイル時環境に適した共有オブジェクトを生成しています。この共有オブジェクト libfoo.so.1 は、次の結合例で使用されます。


$ cc -o libfoo.so.1 -h libfoo.so.1 -M mapfile -G foo.o bar.o ¥ data.o
$ ln -s libfoo.so.1 libfoo.so
$ pvs -dsv libfoo.so.1
        libfoo.so.1:
                _end;
                _GLOBAL_OFFSET_TABLE_;
                _DYNAMIC;
                _edata;
                _PROCEDURE_LINKAGE_TABLE_;
                _etext;
        SUNW_1.1:
                foo1;
                SUNW_1.1;
        SUNW_1.2:                {SUNW_1.1}:
                foo2;
                SUNW_1.2;
        SUNW_1.2.1 [WEAK]:       {SUNW_1.2}:
                SUNW_1.2.1;
        SUNW_1.3a:               {SUNW_1.2}:
                bar1;
                SUNW_1.3a;
        SUNW_1.3b:               {SUNW_1.2}:
                bar2;
                SUNW_1.3b

実際には、この共有オブジェクトによって提供される 6 つの公共インタフェースがあります。これらのインタフェースのうち 4 つ (SUNW_1.1SUNW_1.2SUNW_1.3a、および SUNW_1.3b) は 1 組の関数を定義し、1 つ (SUNW_1.2.1) は共有オブジェクトに対する内部実装の変更を記述し、もう 1 つ (libfoo.so.1) はいくつかの予約ラベルを定義します。このオブジェクトによって作成される動的オブジェクトは、それらが結合するインタフェースがどれかを記録します。

次の例では、両方のシンボル foo1foo2 を参照するアプリケーションを作成しています。アプリケーションに記録されるバージョンアップ依存関係に関する情報は、pvs(1)-r オプションを付けて使用して調べることができます。


$ cat prog.c
extern void foo1();
extern void foo2();

main()
{
        foo1();
        foo2();
}
$ cc -o prog prog.c -L. -R. -lfoo
$ pvs -r prog
        libfoo.so.1 (SUNW_1.2, SUNW_1.2.1);

この例では、アプリケーション prog は、実際に 2 つのインタフェース SUNW_1.1SUNW_1.2 に結合されています。これらのインタフェースが、大域シンボル foo1foo2 をそれぞれ提供したためです。

ただし、バージョン定義 SUNW_1.1 はバージョン定義 SUNW_1.2 から継承されたものとして libfoo.so.1 内に定義されているため、後者のバージョン依存関係だけを記録する必要があります。バージョン定義依存関係のこの正規化によって、オブジェクト内に保持して、実行時に処理する必要があるバージョン情報量は削減されます。

アプリケーション prog は、ウィークバージョン定義 SUNW_1.2.1 を含む共有オブジェクトの実装状態に対して構築されるため、この依存関係も記録されます。このバージョン定義は、バージョン定義 SUNW_1.2 を継承するように定義されていますが、バージョンのウィーク性は SUNW_1.1 によるその正規化を阻害するため、依存関係は別々に記録されます。

相互に継承される複数のウィークバージョン定義がある場合、これらの定義は、ウィークでないバージョン定義と同じ方法で正規化されます。


注 -

バージョン依存関係の記録は、リンカーの -z noversion オプションによって抑制できます。


これらのバージョン定義依存関係の記録を終えると、実行時リンカーは、アプリケーションの実行時に結合されたオブジェクト内に必要なバージョン定義があるかどうかを検査します。この検査は、ldd(1)-v オプションを付けて使用して表示できます。たとえば、アプリケーション prog に対して、ldd(1) を実行すると、バージョン定義依存関係は、共有オブジェクト libfoo.so.1 で正しく検出されることがわかります。


$ ldd -v prog

   find object=libfoo.so.1; required by prog
        libfoo.so.1 =>   ./libfoo.so.1
   find version=libfoo.so.1;
        libfoo.so.1 (SUNW_1.2) =>            ./libfoo.so.1
        libfoo.so.1 (SUNW_1.2.1) =>          ./libfoo.so.1
   ....

注 -

ldd(1)-v オプションを付けると、詳細出力が暗黙のうちに指定されます。この出力では、すべての依存関係の再帰的なリストが、すべてのバージョンアップ条件とともに生成されます。


ウィークでないバージョン定義依存関係を検出できないと、アプリケーションの初期設定中に重大なエラーが起こります。検出できないウィークバージョン定義依存関係は、暗黙の内に無視されます。たとえば、libfoo.so.1 がバージョン定義 SUNW_1.1 だけを含む環境で、アプリケーション prog が実行された場合は、次の重大なエラーが生じます。


$ pvs -dv libfoo.so.1
        libfoo.so.1;
        SUNW_1.1;
$ prog
ld.so.1: prog: fatal: libfoo.so.1: version `SUNW_1.2' not ¥
found (required by file prog)

アプリケーション prog がバージョン定義依存関係を記録しなかった場合は、必要なインタフェースシンボル foo2 が存在しないこと自体が、アプリケーションの実行中に重大な再配置エラーとして現われます (「再配置エラー」を参照)。この再配置エラーは、プロセス初期設定中またはプロセス実行中に生じる可能性があります。また、アプリケーションの実行パスが関数 foo2 を呼び出さなかった場合には、まったく生じないこともあります。

バージョン定義依存関係を記録すると、アプリケーションによって必要なインタフェースが使用可能かどうかがすぐに示されます。

libfoo.so.1 がバージョン定義 SUNW_1.1SUNW_1.2 だけを含む環境内でアプリケーション prog が実行された場合、ウィークでないバージョン定義条件はすべて満たされます。ウィークバージョン定義 SUNW_1.2.1 の不在は重大ではないエラーと見なされるため、実行時エラー条件は生成されません。ただし、ldd(1) を使用すると、検出できないすべてのバージョン定義が表示されます。


$ pvs -dv libfoo.so.1
        libfoo.so.1;
        SUNW_1.1;
        SUNW_1.2:                {SUNW_1.1};
$ prog
string used by foo1()
string used by foo2()
$ ldd prog
        libfoo.so.1 =>   ./libfoo.so.1
        libfoo.so.1 (SUNW_1.2.1) =>          (version not found)
        ...........

注 -

オブジェクトが指定の依存関係からのバージョン定義を必要としている場合、実行時にその依存関係の実装状態にバージョン定義情報が含まれていないことがわかると、依存関係のバージョン検査は暗黙の内に無視されます。この方針は、非バージョンアップ共有オブジェクトからバージョンアップ共有オブジェクトへの移行が行われるときに、下位互換性レベルを提供するものです。ただし、ldd(1) は、バージョン条件の違いを表示するために引き続き使用できます。


追加オブジェクトのバージョンの検査

バージョン定義シンボルも、dlopen(3DL) によって取得されたオブジェクトのバージョン条件を検査するメカニズムとなるものです。この関数を使用してプロセスのアドレス空間に追加されたオブジェクトに対しては、実行時リンカーによる自動バージョン依存関係検査が行われません。このため、この関数の呼び出し元が、バージョンアップ条件が適合しているかどうかを検査する必要があります。

必要なバージョン定義があるかどうかは、dlsym(3DL) を使用して、関連のバージョン定義シンボルを調べることによって検査できます。次の例は、dlopen(3DL) によってプロセスに追加され、SUNW_1.2 が使用可能かどうかを確認される共有オブジェクト libfoo.so.1 を示しています。


#include        <stdio.h>
#include        <dlfcn.h>

main()
{
    void *       handle;
    const char * file = "libfoo.so.1";
    const char * vers = "SUNW_1.2";
    ....

    if ((handle = dlopen(file, RTLD_LAZY)) == NULL) {
            (void) printf("dlopen: %s¥n", dlerror());
            exit (1);
    }

    if (dlsym(handle, vers) == NULL) {
            (void) printf("fatal: %s: version `%s' not found¥n",
                 file, vers);
            exit (1);
    }
    ....