ELF オブジェクトでは、大域シンボルは他のオブジェクトから結合できます。これらの大域シンボルのいくつかは、オブジェクトの「公開インタフェース」を提供するものであると言うことができます。それ以外のシンボルは、オブジェクトの内部実装の一部であり、外部使用を目的としていません。オブジェクトのインタフェースは、ソフトウェアのリリースごとに変更されることがあるので、この変更を識別する方法が必要です。
また、ソフトウェアリリースごとのオブジェクトの内部実装の変更を識別する方法も必要とされます。
インタフェースと実装状態の識別情報はいずれも、オブジェクト内にバージョン定義として記録できます (内部バージョンアップの詳しい説明については、「概要」を参照してください)。
内部バージョンアップは、共有オブジェクトで最も使用されます。これは、変更を記録して、実行中にインタフェースの妥当性検査 (「バージョン定義への結合」を参照) を行えるようにし、さらにアプリケーションによる選択的結合 (「バージョン結合の指定」を参照) を可能にするからです。この章では、共有オブジェクトを例として使用します。
以後の節では、共有オブジェクトに適用されるリンカーが提供する内部バージョンアップ機構の簡単な概要を示します。これらの例では、共有オブジェクトの初期構築からいくつかの一般的な更新の筋書きを通して、共有オブジェクトをバージョンアップするための規約と機構を示しています。
共有オブジェクトは、主番号ファイル接尾辞を含む命名規約に従っています (「命名規約」を参照)。この共有オブジェクト内では、1 つまたは複数のバージョン定義を作成できます。各バージョン定義は、次のいずれかに分類できます。
業界標準インタフェースへ準拠したインタフェースを定義する (たとえば、「System V Application Binary Interface」)
ベンダー特定の「公開」インタフェースを定義する
ベンダー特定の「専用」インタフェースを定義する
オブジェクトの内部実装に対する変更 (ベンダー特定) を定義する
次の「バージョン定義」命名規約は、定義がどの分類に属するのかを示すために役立ちます。
最初の 3 つの分類は、インタフェース定義を示します。これらの定義は、インタフェースを構成する大域シンボル名とバージョン定義名の関連付けからなります (「バージョン定義の作成」を参照)。共有オブジェクト内のインタフェースの変更は、通常「副改訂」と呼ばれます。このため、この種類のバージョン定義には、「副」バージョン番号の接尾辞が付きます。これは、ファイル名の「主」バージョン番号の接尾辞の流儀に基づくものです。
最後の分類は、オブジェクト内の変更を示します。この定義は、名札として機能するバージョン定義からなり、関連するシンボル名はありません。この定義は、ウィークバージョン定義と呼ばれます (「ウィークバージョン定義の作成」を参照)。共有オブジェクト内の実装状態の変更は、通常「マイクロ改訂」と呼ばれます。したがって、このタイプのバージョン定義には内部変更が適用されている以前の「副」番号と同様に、「マイクロ」バージョン番号が接尾辞として付きます。
業界標準インタフェースは、この標準を反映するバージョン定義名を使用しなければなりません。ベンダーインタフェースは、そのベンダー固有のバージョン定義名を使用する必要があります (企業の株式銘柄のシンボルが適していることがあります)。
私用バージョン定義は、使用方法が制限されているかまたは保証されていないシンボルを示します。「private」という語を明確に示すべきです。
バージョン定義を行うと、関連するバージョンシンボル名が必ず作成されます。したがって、一意名と「副」および「マイクロ」の接尾辞規約を使用すると、構築中のオブジェクト内でシンボルが衝突する可能性を減らすことができます。
次の定義例は、これらの命名規約の使用を示しています。
「System V Application Binary Interface」標準インタフェースを定義します。
Solaris 公開インタフェースを定義します。
Solaris 私用インタフェースを定義します。
Solaris 内部実装の変更を示します。
共有オブジェクトのインタフェースを確立するには、まず、共有オブジェクトによって提供される大域シンボルが、3 つのインタフェースバージョン定義分類のうちどれに属するのかを判別します。
業界標準インタフェースシンボルの規約は、公開されたヘッダファイルとベンダーによって提供される関連のマニュアルページに定義されている。また、対応する標準の文献にも記述されている
ベンダーの公開インタフェースシンボルの規約は、公開されたヘッダファイルとベンダーによって提供される関連のマニュアルページに定義されている
ベンダーの私用インタフェースシンボルの定義は、ほとんど、またはまったく公開されていない
これらのインタフェースを定義することによって、ベンダーは、共有オブジェクトの各インタフェースの保証の程度を示します。業界標準およびベンダーが公開している各インタフェースは、リリースが替わっても安定して使用できます。リリースが替わってもアプリケーションが引き続き正しく機能することを知っていれば、これらのインタフェースを自由に安全に結合することができます。
業界標準インタフェースは、他のベンダーによって提供されたシステムでも使用できる可能性があるため、これらのインタフェースを使用するようにアプリケーションを制限することによって、バイナリ互換性を高めることができます。
ベンダー公開インタフェースは、他のベンダーによって提供されたシステムでは使用できない場合がありますが、これらのインタフェースはそれらが提供されたシステムがバージョンアップしても、安定して使用できます。
ベンダー私用インタフェースは、非常に不安定であり、リリースが替わると変更されたり、削除されたりすることもあります。これらのインタフェースが提供する機能は保証されていないか、または実験的なものです。あるいは、ベンダー特定のアプリケーションに対するアクセスだけを提供することを目的としています。いかなる程度のバイナリ互換性を実現したい場合でも、これらのインタフェースの使用を避けるようにしてください。
上記のどれにも分類されない大域シンボルは、局所的な適用範囲に限定して、結合では参照できないようにする必要があります (「シンボル範囲の縮小」を参照してください)。
共有オブジェクトの使用可能インタフェースを決定して、mapfile とリンカーの -M オプションを使用すれば対応するバージョン定義を作成できます (この mapfile 構文の説明については 、「追加シンボルの定義」を参照)。
次の例は、共有オブジェクト libfoo.so.1 にベンダー公開インタフェースを定義しています。
$ cat mapfile SUNW_1.1 { # Release X. global: foo2; foo1; local: *; }; $ cc -G -o libfoo.so.1 -h libfoo.so.1 -z text -M mapfile foo.c |
ここで、大域シンボル foo1 と foo2 は、共有オブジェクト公開インタフェース SUNW_1.1 に割り当てられています。入力ファイルから提供された他の大域シンボルはすべて、自動縮小命令「*」によって局所範囲に縮小されています (「シンボル範囲の縮小」を参照)。
各バージョン定義の mapfile エントリには、更新のリリースまたは日付を反映するコメントを付けてください。この情報は、たとえば複数の開発者によって行なわれた 1 つのオブジェクトに対する複数の変更を 1 つのバージョン定義にまとめて、ソフトウェア製品の一部として共有オブジェクトを配布するのに適切なものに調整するときに役立ちます。
既存の非バージョンアップ共有オブジェクトをバージョンアップするには、特に注意が必要です。これは、以前のソフトウェアリリースで配布された共有オブジェクトが、その大域シンボルのすべてを、他のものと結合できるようにしているためです。共有オブジェクトの意図したインタフェースを判定できる可能性はありますが、ソフトウェア開発者が発見して他のシンボルに結合した可能性もあります。したがって、シンボルを削除すると、新しくバージョンアップされた共有オブジェクトの配布時にアプリケーションに障害が生じる場合があります。
既存の非バージョンアップ共有オブジェクトの内部バージョンアップは、既存アプリケーションを破壊することなく、インタフェースを判定して適用できる場合に実現できます。実行時リンカーのデバッグ機能は、各種のアプリケーションの結合条件を検査するために役立ちます (「デバッギングエイド」を参照)。ただし、この既存結合条件の判定は、共有オブジェクトを使用するすべてのプログラムがわかっているということを前提としています。
既存の非バージョンアップ共有オブジェクトの結合条件を判定できない場合は、新しいバージョンアップ名を使用して、新しい共有オブジェクトファイルを作成する必要があります (「バージョンアップファイル名の管理」を参照)。すべての既存アプリケーションの依存関係を満たすには、この新しい共有オブジェクトだけでなく、元の共有オブジェクトも配布する必要があります。
元の共有オブジェクトの実装を一切変更しない場合は、共有オブジェクトのバイナリをそのまま配布するだけで十分でしょう。しかし、元の共有オブジェクトを更新する必要がある場合 (たとえば、パッチや、新しいプラットフォームとの互換性を保つための実装の変更など) は、代替ソースツリーから共有オブジェクトを作り直した方がいいでしょう。
共有オブジェクトに対する互換性のある変更は、内部バージョンアップによって吸収できます (「インタフェースの互換性」を参照)。すべての互換性のない変更では、新しい外部バージョンアップ名によって、新しい共有オブジェクトを作成する必要があります (「バージョンアップファイル名の管理」を参照)。
内部バージョンアップによって収容できる互換性のある更新は、次の 3 つの基本分類に属します。
新しいシンボルの追加
既存のシンボルに対して新しいインタフェースの作成
内部実装の変更
最初の 2 つは、インタフェースバージョン定義に適切なシンボルを関連付けることによって実現されます。最後のカテゴリは、関連のシンボルを持たないウィークバージョン定義を作成することによって実現されます。
新しい大域シンボルを含む、互換性のある、新しいリリースの共有オブジェクトは、これらのシンボルを新しいバージョン定義に割り当てる必要があります。この新しいバージョン定義は、以前のバージョン定義を継承しなければなりません。
次の mapfile の例では、新しいシンボル foo3 を新しいインタフェースバージョン定義 SUNW_1.2 に割り当てています。この新しいインタフェースは、元のインタフェース SUNW_1.1 を継承します。
$ cat mapfile SUNW_1.2 { # Release X+1. global: foo3; } SUNW_1.1; SUNW_1.1 { # Release X. global: foo2; foo1; local: *; }; |
バージョン定義の継承によって、共有オブジェクトのユーザーすべてに記録する必要があるバージョン情報の量が減ります。
オブジェクトの実装に対する更新からなる、互換性のある新しいリリースの共有オブジェクト (たとえばバグ修正や性能の改善) にはすべて、ウィークバージョン定義を付ける必要があります。この新しいバージョン定義は、更新の発生時に存在する最新のバージョン定義を継承しなければなりません。
次の mapfile の例では、ウィークバージョン定義 SUNW_1.1.1 を生成しています。この新しいインタフェースは、以前のインタフェース SUNW_1.1 によって提供された実装に対して、内部変更が加えられたことを示します。
$ cat mapfile SUNW_1.1.1 { } SUNW_1.1; # Release X+1. SUNW_1.1 { # Release X. global: foo2; foo1; local: *; }; |
同じリリースで内部変更と新しいシンボルの追加が同時に発生した場合は、ウィークバージョンとインタフェースバージョン定義の両方を作成する必要があります。次の例は、バージョン定義 SUNW_1.2 と、同じリリース期間中に追加されたインタフェース変更 SUNW_1.1.1 を示しています。どちらのインタフェースも元のインタフェース SUNW_1.1 を継承します。
$ cat mapfile SUNW_1.2 { # Release X+1. global: foo3; } SUNW_1.1; SUNW_1.1.1 { } SUNW_1.1; # Release X+1. SUNW_1.1 { # Release X. global: foo2; foo1; local: *; }; |
SUNW_1.1 と SUNW_1.1.1 の各バージョン定義へのコメントは、これらが両方とも同じリリースに適用されていることを示しています。
場合によっては、ベンダーインタフェースによって提供されたシンボルが、新しい業界標準に組み込まれることがあります。新しい業界標準インタフェースを作成する場合、共有オブジェクトによって提供された元のインタフェース定義を維持することが重要です。これを実現するには、新しい標準インタフェースおよび元ののインタフェースの定義を構築できる、中間バージョン定義を作成する必要があります。
次の mapfile の例は、新しい業界標準インタフェース STAND.1 の追加を示しています。このインタフェースには、新しいシンボル foo4 と既存のシンボル foo3 および foo1 が含まれます。これらは当初、インタフェース SUNW_1.2 および SUNW_1.1 によって提供されたものです。
$ cat mapfile STAND.1 { # Release X+2. global: foo4; } STAND.0.1 STAND.0.2; SUNW_1.2 { # Release X+1. global: SUNW_1.2; } STAND.0.1 SUNW_1.1; SUNW_1.1.1 { } SUNW_1.1; # Release X+1. SUNW_1.1 { # Release X. global: foo2; local: *; } STAND.0.2; # Subversion - providing for STAND.0.1 { # SUNW_1.2 and STAND.1 interfaces. global: foo3; }; # Subversion - providing for STAND.0.2 { # SUNW_1.1 and STAND.1 interfaces. global: foo1; }; |
ここで、シンボル foo3 と foo1 は、元のインタフェース定義および新しいインタフェース定義を構築するために使用される自身の中間インタフェース定義に取り込まれます。
SUNW_1.2 インタフェースの新しい定義は、各自のバージョン定義シンボルを参照しています。この参照がないと、SUNW_1.2 インタフェースには即時シンボル参照が含まれないため、ウィークバージョン定義として分類されます。
シンボル定義を標準インタフェースに併合する場合、元のインタフェース定義が引き続き同じシンボル列を表わすことが求められます。これは、pvs(1) を使用して検査できます。次の例は、SUNW_1.2 インタフェースがソフトウェアリリース X+1 に存在する場合のシンボル列を示しています。
$ pvs -ds -N SUNW_1.2 libfoo.so.1 SUNW_1.2: foo3; SUNW_1.1: foo2; foo1; |
ソフトウェアリリース X+2 での新しい標準インタフェースの導入によって、使用可能なインタフェースバージョン定義は変更されますが、元の各インタフェースによって提供されたシンボル列はそのままです。次の例は、インタフェース SUNW_1.2 が引き続きシンボル foo1、foo2、および foo3 を提供することを示しています。
$ pvs -ds -N SUNW_1.2 libfoo.so.1 SUNW_1.2: STAND.0.1: foo3; SUNW_1.1: foo2; STAND.0.2: foo1; |
アプリケーションが、新しい副バージョンの 1 つだけを参照する場合があります。この場合、以前のリリースでこのアプリケーションを実行しようとすると、実行時バージョンアップエラーが生じます (「バージョン定義への結合」を参照)。
この場合、アプリケーションバージョン結合は、既存のバージョン名を直接参照することによって昇格することができます (「追加バージョン定義への結合」を参照)。
たとえば、アプリケーションが、共有オブジェクト libfoo.so.1 からシンボル foo1 だけを参照する場合、そのバージョン参照は STAND.0.2 に対して行われます。このアプリケーションを以前のリリースで実行できるようにするには、バージョン制御 mapfile 指示を使用して、バージョン結合を SUNW_1.1 に昇格します。
$ cat prog.c extern void foo1(); main() { foo1(); } $ cc -o prog prog.c -L. -R. -lfoo $ pvs -r prog libfoo.so.1 (STAND.0.2); $ cat mapfile libfoo.so - SUNW_1.1 $ADDVERS=SUNW_1.1; $ cc -M mapfile -o prog prog.c -L. -R. -lfoo $ pvs -r prog libfoo.so.1 (SUNW_1.1); |
新しい標準バイナリインタフェースの導入はめったになく、またほとんどのアプリケーションが多くのシンボルをインタフェースバージョンに関係なく参照するため、実際には、この方法でバージョン結合を昇格させる必要はほとんどありません。