リンカーによって処理されるオブジェクトには、他のオブジェクトを結合できる多数の大域シンボルがあります。これらのシンボルは、オブジェクトのアプリケーションバイナリインタフェース (ABI) を記述するものです。オブジェクトの展開中、このインタフェースは、大域シンボルの追加または削除が原因で変更されることがあります。また、オブジェクト展開には、内部実装の変更が関与することがあります。
バージョンアップとは、インタフェースや実装状態の変更を示すためにオブジェクトに適用できるいくつかの手法のことをいいます。これらの手法を使用すると、下位互換性を保ちながらオブジェクト制御による展開を行うことができます。
この章では、オブジェクトの ABI の定義方法について説明し、このインタフェースに対する変更によって下位互換性が受ける影響を分類します。また、インタフェースと実装状態の変更を新しいリリースのオブジェクトに組み込むためのモデルを示します。
この章では、動的実行可能ファイルと共有オブジェクトの実行時インタフェースを中心に説明します。これらの動的オブジェクト内での変更を記述して管理するために使用される手法は、一般的な用語で説明してあります。共有オブジェクトに適用される命名規約とバージョンアップシナリオの共通セットは、付録 B 「バージョンアップの手引き」 に示してあります。
動的オブジェクトの開発者は、インタフェース変更の結果に注意し、特に以前のオブジェクトとの下位互換性を維持するという点で、これらの変更の管理方法を理解する必要があります。
動的オブジェクトによって使用可能になった大域シンボルは、オブジェクトの公共インタフェースを表わします。リンク編集の最後でオブジェクトに残る大域シンボルの数は、公共にしたいと望む数を超える場合がよくあります。これらの大域シンボルは、そのオブジェクトの構築に使用された再配置可能オブジェクトの間で必要な相互関係から引き出されるものであり、オブジェクト自体の専用インタフェースを表わします。
オブジェクトのバイナリインタフェースを定義するには、まず、作成中のオブジェクトから公共に使用できるようにする大域シンボルだけを定義します。これらの公共シンボルは、リンカーの -M オプションと関連の mapfile を最終リンク編集の一部として使用することによって確立できます。この手法は、「シンボル範囲の縮小」に説明されています。この公共インタフェースは、1 つまたは複数のバージョン定義を作成中のオブジェクト内に確立し、オブジェクトの展開時に新しいインタフェースを追加するための基礎を作ります。
次の節は、この初期公共インタフェースに基づいて説明されています。最初に、インタフェースへの各種の変更をどのように分類すると、適切な管理を行えるかを理解しておくと便利です。
オブジェクトにはさまざまな変更を加えることができます。これらの変更は、単純に次の 2 つのグループに分類することができます。
互換性のある変更。これらの変更は、今まで使用できたインタフェースがすべてそのままの状態で残されるという点で付加的なもの
互換性のない変更。これらの変更は既存インタフェースを変更したため、そのインタフェースの既存ユーザーはそれを使用できないか、または動作が異なる
次のリストは、共通のオブジェクト変更のいくつかを上記のカテゴリのどちらかに分類しています。
シンボルの追加 - 互換性のある変更
シンボルの削除 - 互換性のない変更
非 varargs(3HEAD) 関数への引数の追加 - 互換性のない変更
関数からの引数の削除 - 互換性のない変更
関数への、または外部定義としてのデータ項目のサイズまたは内容の変更 - 互換性のない変更
バグ修正または関数の内部拡張 - オブジェクトの意味プロパティを変更しない場合は互換性のある変更。変更する場合は互換性のない変更
シンボルを追加すると、割り込みが原因で新しいシンボルがアプリケーションによるそのシンボルの使用法と矛盾するという互換性のない変更が生じる可能性があります。ただし、通常ソースレベル名前空間の管理が使用されるため、実際にはこの可能性はめったにありません。
互換性のある更新は、バージョン定義を生成されたオブジェクトの「内部」に維持することにより調整できます。互換性のない変更は、新しい「外部」バージョンアップ名によって新しいオブジェクトを作成することにより調整できます。これらのバージョンアップ手法を使用すると、実行時の正しいバージョン割り当てを検査できるだけでなく、アプリケーションの選択的割り当てを行うことができます。これらの 2 つの手法については、次の節でさらに詳しく説明します。
動的オブジェクトには、1 つまたは複数の内部バージョン定義を関連付けることができます。各バージョン定義は通常、1 つまたは複数の名前に関連付けられます。シンボル名は、1 つのバージョン定義にしか関連付けられませんが、バージョン定義は他のバージョン定義からシンボルを継承できます。したがって、1 つまたは複数の独立した、または関連するバージョン定義を作成中のオブジェクト内に定義するための構造が存在します。オブジェクトに新しい変更が加えられたら、新しいバージョン定義を追加してこれらの変更を表現することができます。
共有オブジェクト内にバージョン定義を与えた場合、次の 2 つの結果が得られます。
この共有オブジェクトに対して構築された動的オブジェクトは、それらが結合されているバージョン定義への依存関係を記録できる。これらのバージョンの依存関係は、アプリケーションの正しい実行に適切なインタフェースまたは機能を使用できるかどうかを確認するため、実行時に検査される
動的オブジェクトは、そのリンク編集中に結合先として必要な共有オブジェクトのバージョン定義だけを選択できる。このメカニズムを使用すると、開発者は、最も自由な操作を可能にするインタフェースまたは機能に合わせて、共有オブジェクトへの依存関係を調整することができる
バージョン定義は、一般にシンボル名と一意のバージョン名との関連付けからなります。これらの関連付けは、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 は共有オブジェクトの公共インタフェースを提供するために定義された唯一の大域シンボルです。特殊な自動縮小命令「*」は、他の大域シンボルすべてを縮小することによって、生成中のオブジェクト内にローカル結合が生じるようにします (この命令については、「追加シンボルの定義」を参照)。関連バージョン名 SUNW_1.1 は、バージョン定義を生成させます。したがって、共有オブジェクトの公共インタフェースは、大域シンボル foo1 に関連付けられた内部バージョン定義 SUNW_1.1 からなります。
バージョン定義、または自動縮小命令によってオブジェクトが生成されると、基本バージョンも必ず作成されます。この基本バージョンは、ファイル自体の名前を使用して定義され、リンカーによって生成された予約シンボルすべてを関連付けるために使用されます (予約シンボルのリストについては 「出力イメージの生成」を参照)。
オブジェクト内に含まれるバージョン定義は、pvs(1) に -d オプションを付けて使用して表示できます。
$ 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}; |
次の節では、これらのバージョン定義記録を使用して、実行時結合の条件を検査し、オブジェクトの作成中にその結合を制御する方法について説明します。
動的実行可能ファイルまたは共有オブジェクトが、他の共有オブジェクトに対して構築される場合、これらの依存関係は結果オブジェクトに記録されます (詳細は、「共有オブジェクトの処理」と 「共有オブジェクト名の記録」を参照)。これらの共有オブジェクトの依存関係にバージョン定義も含まれる場合、関連のバージョン依存関係は結果オブジェクトに記録されます。
次の例は、前述のデータファイルを取り上げて、コンパイル時環境に適した共有オブジェクトを生成しています。この共有オブジェクト 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.1、SUNW_1.2、SUNW_1.3a、および SUNW_1.3b) は 1 組の関数を定義し、1 つ (SUNW_1.2.1) は共有オブジェクトに対する内部実装の変更を記述し、もう 1 つ (libfoo.so.1) はいくつかの予約ラベルを定義します。このオブジェクトによって作成される動的オブジェクトは、それらが結合するインタフェースがどれかを記録します。
次の例では、両方のシンボル foo1 と foo2 を参照するアプリケーションを作成しています。アプリケーションに記録されるバージョンアップ依存関係に関する情報は、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.1 と SUNW_1.2 に結合されています。これらのインタフェースが、大域シンボル foo1 と foo2 をそれぞれ提供したためです。
ただし、バージョン定義 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.1 と SUNW_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); } .... |
バージョン定義を含む共有オブジェクトに対して動的オブジェクトを作成する場合、リンカーに対して、特定のバージョン定義への結合を制限するように指示することができます。リンカーを使用すると、特定インタフェースへのオブジェクトの結合を効果的に制御することができます。
オブジェクトの結合条件は、ファイル制御命令によって制御できます。この命令は、リンカーの -M オプションと関連の mapfile を使用して提供されます。これらのファイル制御 mapfile 命令の構文は、次のとおりです。
name - version [ version ... ] [ $ADDVERS=version ]; |
Name - 共有オブジェクト依存関係の名前を表す。この名前は、リンカーによって使用される共有オブジェクトのコンパイル環境名と一致しなければなりません (「ライブラリの命名規約」を参照)
Version - 結合に使用可能でなければならない共有オブジェクト内のバージョン定義名を表わす。複数のバージョン定義を指定できる
次のように、この結合制御が役立つシナリオがいくつかあります。
共有オブジェクトが、一意の依存しないバージョンを定義するようにバージョンアップされていて、異なる標準インタフェースを定義する可能性が高い場合、アプリケーションでは、その結合が特定インタフェースの条件を満たすように保証できる
共有オブジェクトがいくつかのソフトウェアリリースに対してバージョンアップされている場合、アプリケーション開発者は、前のソフトウェアリリースで使用可能であったインタフェースだけを使用するように制限できる。したがって、アプリケーションインタフェースの条件が共有オブジェクトの以前のリリースと一致するという知識を前提として、最新リリースの共有オブジェクトを使用してアプリケーションを構築することができる
次に、バージョン制御機構の使用例を示します。この例では、次のバージョンインタフェース定義を含む共有オブジェクト libfoo.so.1 を使用しています。
$ pvs -dsv libfoo.so.1 libfoo.so.1: _end; _GLOBAL_OFFSET_TABLE_; _DYNAMIC; _edata; _PROCEDURE_LINKAGE_TABLE_; _etext; SUNW_1.1: foo1; foo2; SUNW_1.1; SUNW_1.2: {SUNW_1.1}: bar; |
バージョン定義 SUNW_1.1 および SUNW_1.2 は、ソフトウェア Release X および Release X+1 で使用可能な libfoo.so.1 内のインタフェースをそれぞれ表わします。
アプリケーションは、次のバージョン制御 mapfile 命令を使用して、Release X で使用可能なインタフェースだけに結合するように構築できます。
$ cat mapfile libfoo.so - SUNW_1.1; |
たとえば、アプリケーション prog を開発する場合、アプリケーションが Release で実行されるようにすると、アプリケーションではそのリリースで使用可能なインタフェースだけを使用できます。アプリケーションがシンボル bar を間違って参照すると、そのアプリケーションが必要なインタフェースに準拠していないことが、未定義のシンボルエラーとして、リンカーによって通知されます。
$ cat prog.c extern void foo1(); extern void bar(); main() { foo1(); bar(); } $ cc -o prog prog.c -M mapfile -L. -R. -lfoo Undefined first referenced symbol in file bar prog.o (symbol belongs to unavailable ¥ version ./libfoo.so (SUNW_1.2)) ld: fatal: Symbol referencing errors. No output written to prog |
SUNW_1.1 インタフェースに準拠するには、bar への参照を削除する必要があります。これは、アプリケーションを再処理して bar に対する条件を削除するか、または bar の実装をアプリケーションの作成に追加することによって行います。
通常のオブジェクトシンボル結合からバージョン依存関係を作成するよりも、追加バージョン依存関係を記録した方がよい場合があります。これは、$ADDVERS ファイル制御命令を使用して実行できます。この節では、この追加結合が役に立ついくつかのシナリオについて説明します。
libfoo.so.1 の例に続いて、Release X+2 において、バージョン定義 SUNW_1.1 が 2 つの標準リリース STAND_A と STAND_B に分割される場合を想定します。互換性を維持するには、SUNW_1.1 バージョン定義を維持する必要がありますが、ここでは、2 つの標準定義を継承するものとして表わされています。
$ pvs -dsv libfoo.so.1 libfoo.so.1: _end; _GLOBAL_OFFSET_TABLE_; _DYNAMIC; _edata; _PROCEDURE_LINKAGE_TABLE_; _etext; SUNW_1.1: {STAND_A, STAND_B}: SUNW_1.1; SUNW_1.2: {SUNW_1.1}: bar; STAND_A: foo1; STAND_A; STAND_B: foo2; STAND_B; |
アプリケーション prog の唯一の条件がインタフェースシンボル foo1 であるようにその作成を続けた場合、このアプリケーションはバージョン定義 STAND_A に対して単一の依存関係を持ちます。このことは、インタフェース foo1 が以前のリリースで存在したけれども、バージョン定義 STAND_A は存在しなかったために libfoo.so.1 が Release X+2 よりも小さいシステムでの prog の実行を阻害します。
したがって、アプリケーション prog は、次のファイル制御命令を使用して SUNW_1.1 に対する依存関係を作成することによって、その条件を以前のリリースに合わせて構築できます。
$ cat mapfile libfoo.so - SUNW_1.1 $ADDVERS=SUNW_1.1; $ cat prog extern void foo1(); main() { foo1(); } $ cc -M mapfile -o prog prog.c -L. -R. -lfoo $ pvs -r prog libfoo.so.1 (SUNW_1.1); |
この明示的な依存関係は、真の依存関係の条件をカプセル化し、旧リリースとの互換性を保つのに十分なものです。
「ウィークバージョン定義の作成」では、ウィークバージョン定義を使用して、内部実装の変更をマークする方法について説明しました。これらのバージョン定義は、オブジェクトに対して行われたバグ修正と性能の改善に適しています。アプリケーションを正しく実行するためにウィークバージョンが必要な場合は、このバージョン定義への明示的な依存関係を生成できます。
バグ修正や性能の改善がアプリケーションを正しく機能させるために重要な場合は、このような依存関係の確立も重要になります。
引き続き libfoo.so.1 の例で、バグ修正がウィークバージョン定義 SUNW_1.2.1 としてソフトウェア Release X+3 に組み込まれている場合を想定します。
$ pvs -dsv libfoo.so.1 libfoo.so.1: _end; _GLOBAL_OFFSET_TABLE_; _DYNAMIC; _edata; _PROCEDURE_LINKAGE_TABLE_; _etext; SUNW_1.1: {STAND_A, STAND_B}: SUNW_1.1; SUNW_1.2: {SUNW_1.1}: bar; STAND_A: foo1; STAND_A; STAND_B: foo2; STAND_B; SUNW_1.2.1 [WEAK]: {SUNW_1.2}: SUNW_1.2.1; |
通常、アプリケーションは、この共有オブジェクトに対して構築されている場合、バージョン定義 SUNW_1.2.1 に対するウィークである依存関係を記録します。この依存関係は情報提供だけを目的とするものであり、バージョン定義が実行時に使用される libfoo.so.1 に見つからなくても、アプリケーションを終了させません。
ファイル制御命令 $ADDVERS を使用すると、バージョン定義に対する明示的な依存関係を生成できます。この定義がウィークである場合、この明示的参照によって、バージョン定義が強い依存関係に高められます。
したがって、アプリケーション prog は、次のファイル制御命令を使用して、SUNW_1.2.1 インタフェースを実行時に使用できるという条件を実施するように構築できます。
$ cat mapfile libfoo.so - SUNW_1.1 $ADDVERS=SUNW_1.2.1; $ cat prog extern void foo1(); main() { foo1(); } $ cc -M mapfile -o prog prog.c -L. -R. -lfoo $ pvs -r prog libfoo.so.1 (SUNW_1.2.1); |
ここで、prog は、インタフェース STAND_A に対する明示的な依存関係によって構築されています。バージョン定義 SUNW_1.2.1 は、強いバージョンに高められているため、依存関係 STAND_A によっても正規化されます。実行時にバージョン定義 SUNW_1.2.1 が見つからないと、重大なエラーが生成されます。
1 つまたは 2 つの依存関係を処理する場合、バージョン定義への明示的な結合は、バージョン定義シンボルを参照することによっても達成できます。この処理は、リンカーの -u オプションを使用すると、最も簡単に実行できます。ただし、シンボル参照は非選択的であり、類似の名前を持つ複数のバージョン定義を含む可能性がある複数の依存関係を処理する場合は、この手法で明示的な結合を作成することはできません。
この章のこれまでの情報から、明らかに、個々のバージョンの定義がそのオブジェクトの使用期間にわたって変更されなければ、1 つのオブジェクト内の複数のバージョンに結合する各種のモデルは損なわれません。
一度オブジェクトに対してバージョン定義を作成して公開したら、そのバージョン定義は、そのオブジェクトの次のリリースでも変更されずに存在していなければなりません。バージョン名およびそれに関連するシンボルは両方とも変更しないでください。このため、バージョン定義内で定義されるシンボル名には、ワイルドカードによる拡張はサポートされていません。これは、ワイルドカードに当てはまるシンボルの数が、オブジェクトが発展する過程で異なる場合があるからです。
前の節では、動的オブジェクト内でバージョン情報を記録して使用する方法について説明しました。再配置可能オブジェクトは、同様の方法でバージョンアップ情報を保持できますが、これらの情報の使用方法に多少違いがあります。
再配置可能オブジェクトのリンク編集に提供されるバージョン定義はすべて、前の例で説明したものとまったく同じ形式で記録されます。ただしデフォルトにより、作成中のオブジェクトに対するシンボル削減は実行されません。代わりに、再配置可能オブジェクトが動的オブジェクトの生成に対して最終的に入力として使用されると、バージョン記録自体が、適用するシンボル削減を判定するために使用されます。
また、再配置可能オブジェクトで検出されたバージョン定義はすべて、動的オブジェクトに伝達されます。再配置可能オブジェクトでのバージョン処理の例については、「シンボル範囲の縮小」を参照してください。
共有オブジェクトへの実行時参照は、常にファイルのバージョンファイル名を参照しなければなりません。通常、これはバージョン番号が接尾辞として付いたファイル名として表わされます。共有オブジェクトのインタフェースが互換性のない方法で (古いアプリケーションを破壊するような方法で) 変更される場合は、新しい共有オブジェクトを新しいバージョンアップファイル名によって配布する必要があります。また、元のバージョンアップファイル名も配布して、古いアプリケーションで必要なインタフェースを提供する必要があります。
共有オブジェクトを個別のバージョンアップファイル名として実行時環境内に提供すると、一連のソフトウェアリリースに対して構築されたアプリケーションで、それらを構築する基本となったインタフェースを実行中の結合に使用できるようにすることができます。
次の節では、コンパイル環境と実行時環境間でのインタフェースの結合を同期する方法について説明します。
「命名規約」では、リンク編集中に共有オブジェクトを入力するための最も一般的な手法は、-l オプションを使用する方法であると述べました。このオプションは、リンカーのライブラリ検索機構を使用して接頭辞 lib と接尾辞 .so が付いた共有オブジェクトを探します。
ただし、実行時に、共有オブジェクト依存関係は、そのバージョンアップファイル名形式で存在していなければなりません。これらのオブジェクトのバージョンを管理するための最も一般的な手法は、これらの命名規約に従う 2 つの異なる共有オブジェクトを維持する方法ではなく、2 つのファイル名間にファイルシステムリンクを作成する方法です。
実行時共有オブジェクト libfoo.so.1 をコンパイル環境で使用できるようにするには、コンパイルファイル名から実行時ファイル名にシンボルリンクを与える必要があります。次に例を示します。
$ cc -o libfoo.so.1 -G -K pic foo.c $ ln -s libfoo.so.1 libfoo.so $ ls -l libfoo* lrwxrwxrwx 1 usr grp 11 1991 libfoo.so -> libfoo.so.1 -rwxrwxr-x 1 usr grp 3136 1991 libfoo.so.1 |
シンボルリンクまたはハードリンクを使用できます。ただしマニュアルおよび診断ツールとしては、シンボルリンクの方が有効です。
ここで、共有オブジェクト libfoo.so.1 は、実行時環境に対して生成されています。シンボルリンク libfoo.so の生成は、コンパイル環境でのこのファイルの使用も有効にしています。次に例を示します。
$ cc -o prog main.o -L. -lfoo |
ここで、リンカーは、シンボルリンク libfoo.so を追って見つける共有オブジェクト libfoo.so.1 によって記述されたインタフェースを使用して、再配置可能オブジェクト main.o を処理します。
一連のソフトウェアリリースに対して、この共有オブジェクトの新しいバージョンが変更されたインタフェースによって配布される場合、コンパイル環境はシンボルリンクを変更することで適用可能なインタフェースを使用するように構築することができます。次に例を示します。
$ ls -l libfoo* lrwxrwxrwx 1 usr grp 11 1993 libfoo.so -> libfoo.so.3 -rwxrwxr-x 1 usr grp 3136 1991 libfoo.so.1 -rwxrwxr-x 1 usr grp 3237 1992 libfoo.so.2 -rwxrwxr-x 1 usr grp 3554 1993 libfoo.so.3 |
ここでは、共有オブジェクトの 3 つの主要バージョンが使用できます。これらの共有オブジェクトのうち、libfoo.so.1 と libfoo.so.2 の 2 つは、既存アプリケーションに対する依存関係を提供します。libfoo.so.3 は、新しいアプリケーションを作成して実行するための最新主要リリースを提供します。
このシンボルリンク機構自体を使用するだけでは、コンパイル環境での使用から実行時環境での条件に合わせて共有オブジェクトを正しく結合することはできません。例が示しているように、リンカーは、動的実行可能ファイル prog に、それが処理した共有オブジェクトのファイル名を記録します。この場合、これはコンパイル環境のファイル名です。
$ dump -Lv prog prog: **** DYNAMIC SECTION INFORMATION **** .dynamic: [INDEX] Tag Value [1] NEEDED libfoo.so ......... |
つまり、アプリケーション prog が実行されると、実行時リンカーは、依存関係 libfoo.so を検索し、結果として、これはこのシンボルリンクが指すすべてのファイルに結合されます。
依存関係として記録される正しい実行時名を指定するには、共有オブジェクト libfoo.so.1 を soname 定義によって構築する必要があります。この定義は、共有オブジェクトの実行時名を識別するもので、この共有オブジェクトに対してリンクするすべてのオブジェクトによって、依存関係名として使用されます。この定義は、共有オブジェクト自体のリンク編集中に -h オプションを使用して与えることができます。次に例を示します。
$ cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 foo.c $ ln -s libfoo.so.1 libfoo.so $ cc -o prog main.o -L. -lfoo $ dump -Lv prog prog: **** DYNAMIC SECTION INFORMATION **** .dynamic: [INDEX] Tag Value [1] NEEDED libfoo.so.1 ......... |
このシンボルリンクと soname 機構は、コンパイル環境と実行時環境の共有オブジェクト命名規約の間に強固な同期を確立しました。これによって、リンク編集中に処理されたインタフェースは、生成された出力ファイルに正確に記録されます。この記録によって、意図したインタフェースが実行時に提供されます。
注意: 外部的にバージョン定義された共有オブジェクトを新しく作成することは重大な変更であり、この共有オブジェクトを使用するすべての処理の完全な依存関係を理解するために注意が必要です。
たとえば、あるアプリケーションが、libfoo.so.1 および外部から記述されたオブジェクト libISV.so.1 と依存関係があるとします。この後者のオブジェクトは libfoo.so.1 とも依存関係があるとします。外部オブジェクト libISV.so.1 の使用には何の変更も加えずに、libfoo.so.2 内の新しいインタフェースを使用するためにアプリケーションを再設計すると、libfoo.so の主要なバージョンが両方とも実行プロセスに含まれることになります。libfoo.so のバージョンを変更する理由は互換性のない変更をマークすることだけなので、プロセス内にオブジェクトの両方のバージョンを持つことは、不正なシンボル結合が発生する原因となり、そのために望ましくない相互作用を引き起こすことがあります。