バージョン定義は、一般にシンボル名と一意のバージョン名との関連付けからなります。これらの関連付けは、mapfile 内に確立され、リンカーの -M オプションを使用して、オブジェクトの最終リンク編集に与えられます。この手法については、「シンボル範囲の縮小」を参照してください。
バージョン定義は、バージョン名が mapfile 指令の一部として指定されている場合は必ず確立されます。次の例では、2 つのソースファイルが mapfile 指令とともに結合されて、定義済み公開インタフェースを持つオブジェクトを作成しています。
$ cat foo.c extern const char * _foo1; void foo1() { (void) printf(_foo1); } $ cat data.c const char * _foo1 = "string used by foo1()\n"; $ cat mapfile SUNW_1.1 { # Release X global: foo1; local: *; }; $ cc -o libfoo.so.1 -M mapfile -G foo.o data.o $ nm -x libfoo.so.1 | grep "foo.$" [33] |0x0001058c|0x00000004|OBJT |LOCL |0x0 |17 |_foo1 [35] |0x00000454|0x00000034|FUNC |GLOB |0x0 |9 |foo1 |
シンボル foo1 は、共有オブジェクトの公開インタフェースを提供するために定義された唯一の大域シンボルです。特殊な自動縮小指令「*」は、ほかの大域シンボルすべてを縮小することによって、生成中のオブジェクト内にローカル結合が生じるようにします。この自動縮小指令については、「mapfile を使用した追加シンボルの定義」を参照してください。関連バージョン名 SUNW_1.1 は、バージョン定義を生成させます。したがって、共有オブジェクトの公開インタフェースは、内部バージョン定義 SUNW_1.1 に関連付けられた大域シンボル foo1 で構成されます。
バージョン定義または自動縮小指令によってオブジェクトが生成されると、基本バージョン定義も必ず作成されます。この基本バージョンは、作成されるオブジェクトの名前を使用して定義されます。この基本バージョンは、リンカーによって生成された予約シンボルすべてを関連付けるために使用されます。予約シンボルのリストについては、「出力ファイルの生成」を参照してください。
オブジェクト内に含まれるバージョン定義は、-d オプションを付けた pvs(1) を使用して表示できます。
$ pvs -d libfoo.so.1 libfoo.so.1; SUNW_1.1; |
オブジェクト libfoo.so.1 には、基本バージョン定義 libfoo.so.1 とともに、SUNW_1.1 という名前の内部バージョン定義があります。
リンカーの -z noversion オプションを使用すると、mapfile 指令のシンボル縮小を実行できますが、バージョン定義の作成は抑制されます。
この初期バージョン定義から、新しいインタフェースと更新された機能を追加することによって、オブジェクトを展開させることができます。たとえば、新機能 foo2 は、それがサポートするデータ構造とともに、ソースファイル foo.c および data.c を更新することによってオブジェクトに追加することができます。
$ cat foo.c extern const char * _foo1; extern const char * _foo2; void foo1() { (void) printf(_foo1); } void foo2() { (void) printf(_foo2); } $ cat data.c const char * _foo1 = "string used by foo1()\n"; const char * _foo2 = "string used by foo2()\n"; |
新しいバージョン定義 SUNW_1.2 を作成すると、シンボル foo2 を表す新しいインタフェースを定義できます。また、この新しいインタフェースは、元のバージョン定義 SUNW_1.1 を継承するように定義できます。
この新しいインタフェースにはオブジェクトの展開を記述できるため、このインタフェースを作成することは重要です。ユーザーはこれらのインタフェースを使って、結合先のインタフェースを検査して選択できます。これらの概念については、「バージョン定義への結合」と「バージョン結合の指定」で詳しく説明します。
次の例は、これらの 1 つのインタフェースを作成する mapfile 指令を示しています。
$ cat mapfile SUNW_1.1 { # Release X global: foo1; local: *; }; SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; $ cc -o libfoo.so.1 -M mapfile -G foo.o data.o $ nm -x libfoo.so.1 | grep "foo.$" [33] |0x00010644|0x00000004|OBJT |LOCL |0x0 |17 |_foo1 [34] |0x00010648|0x00000004|OBJT |LOCL |0x0 |17 |_foo2 [36] |0x000004bc|0x00000034|FUNC |GLOB |0x0 |9 |foo1 [37] |0x000004f0|0x00000034|FUNC |GLOB |0x0 |9 |foo2 |
foo1 と foo2 は、いずれも共有オブジェクトの公開インタフェースの一部として定義されています。ただし、これらのシンボルはそれぞれ別のバージョン定義に割り当てられます。foo1 は、バージョン SUNW_1.1 に割り当てられます。foo2 は、バージョン SUNW_1.2 に割り当てられます。
これらのバージョン定義、その継承、およびそのシンボル関連付けは、pvs(1) に -d、-v、および -s オプションをつけて表示できます。
$ 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 は、バージョン定義 SUNW_1.1 に対する依存関係を持っています。
あるバージョン定義から別のバージョン定義への継承は、便利な手法です。この継承によって、バージョン依存関係に結合するオブジェクトによって最終的に記録されるバージョン情報が削減されます。バージョン継承については、「バージョン定義への結合」で詳しく説明します。
バージョン定義シンボルが作成され、バージョン定義に関連付けられます。pvs(1) の例で示したように、これらのシンボルは -v オプションを使用して表示されます。
オブジェクトに対する新しいインタフェース定義の照会を必要としない内部変更は、ウィークバージョン定義を作成することによって定義できます。このような変更の例としては、バグ修正や性能の改善があります。このようなバージョン定義は空です。このバージョン定義には、大域インタフェースシンボルが関連付けられません。
たとえば、以前の例で使用したデータファイル data.c が、次のようにより詳しい文字列定義を提供するように更新されたとします。
$ cat data.c const char * _foo1 = "string used by function foo1()\n"; const char * _foo2 = "string used by function foo2()\n"; |
ウィークバージョン定義を照会すると、この変更を次のように識別できます。
$ cat mapfile SUNW_1.1 { # Release X global: foo1; local: *; }; SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; SUNW_1.2.1 { } SUNW_1.2; # Release X+2 $ cc -o libfoo.so.1 -M mapfile -G foo.o data.o $ pvs -dv libfoo.so.1 libfoo.so.1; SUNW_1.1; SUNW_1.2: {SUNW_1.1}; SUNW_1.2.1 [WEAK]: {SUNW_1.2}; |
空のバージョン定義は、ウィークラベルによって示されます。これらのウィークバージョン定義を使用すると、アプリケーションは特定の実装詳細の存在を検査できます。アプリケーションは、必要とする実装詳細に関連付けられたバージョン定義に結合できます。「バージョン定義への結合」では、これらの定義を使用する方法について詳しく説明します。
以前の例は、オブジェクトに追加された新しいバージョン定義は、既存のバージョン定義をどのように継承するかを示しています。一意の依存しないバージョン定義を作成することもできます。次の例では、2 つの新しいファイル bar1.c と bar2.c がオブジェクト libfoo.so.1 に追加されています。これらのファイルは、2 つの新しいシンボル bar1 と bar2 をそれぞれ提供します。
$ cat bar1.c extern void foo1(); void bar1() { foo1(); } $ cat bar2.c extern void foo2(); void bar2() { foo2(); } |
これらの 2 つのシンボルは、2 つの新しい公開インタフェースの定義を目的としています。新しいインタフェースはどちらも相互に関連がありません。ただし、それぞれのインタフェースは、元の SUNW_1.2 インタフェースへの依存関係を表します。
次の mapfile 定義は、必要な関連付けを作成します。
$ cat mapfile SUNW_1.1 { # Release X global: foo1; local: *; }; SUNW_1.2 { # Release X+1 global: foo2; } SUNW_1.1; SUNW_1.2.1 { } SUNW_1.2; # Release X+2 SUNW_1.3a { # Release X+3 global: bar1; } SUNW_1.2; SUNW_1.3b { # Release X+3 global: bar2; } SUNW_1.2; |
この mapfile を使用して libfoo.so.1 に作成されたバージョン定義とそれらに関連する依存関係は、pvs(1) を使用して検査できます。
$ cc -o libfoo.so.1 -M mapfile -G foo.o bar1.o bar2.o data.o $ pvs -dv libfoo.so.1 libfoo.so.1; SUNW_1.1; SUNW_1.2: {SUNW_1.1}; SUNW_1.2.1 [WEAK]: {SUNW_1.2}; SUNW_1.3a: {SUNW_1.2}; SUNW_1.3b: {SUNW_1.2}; |
バージョン定義を使用して、実行時結合の要件を検査できます。また、バージョン定義を使用して、オブジェクトの作成中にオブジェクトの結合を制御することもできます。次の節では、これらのバージョン定義の使用方法について詳細に説明します。