ELF オブジェクトでは、大域シンボルは他のオブジェクトから結合できます。これらの大域シンボルのいくつかは、オブジェクトの「公開インタフェース」の提供者として機能します。それ以外のシンボルは、オブジェクトの内部実装の一部であり、外部使用を目的としていません。オブジェクトのインタフェースは、ソフトウェアのリリースごとに変更されることがあります。そのため、変更点を識別することは重要です。
また、ソフトウェアリリースごとのオブジェクトの内部実装の変更を識別する方法も必要とされます。
インタフェースと実装状態の識別情報はいずれも、オブジェクト内に「バージョン定義」として記録できます。内部バージョン管理の詳細は、第 5 章「アプリケーションバイナリインタフェースとバージョン管理」を参照してください。
内部バージョン管理が最も利用されるのは、共有オブジェクトです。これは、変更を記録して、実行中にインタフェースの妥当性検査 (バージョン定義への結合を参照) を行えるようにし、さらにアプリケーションによる選択的結合 (バージョン結合の指定を参照) を可能にするからです。この付録では、共有オブジェクトを例として使用します。
以後の節では、共有オブジェクトに適用されるリンカーが提供する内部バージョンアップ機構の簡単な概要を示します。これらの例では、共有オブジェクトの初期構築からいくつかの一般的な更新の筋書きを通して、共有オブジェクトをバージョンアップするための規約と機構を示しています。
共有オブジェクトは、メジャー (major) ナンバーファイル接尾辞を含むという命名規約に従います(命名規約を参照)。この共有オブジェクト内では、1 つまたは複数のバージョン定義を作成できます。各バージョン定義は、次のいずれかに分類できます。
業界標準インタフェースへ準拠したインタフェース (「System V アプリケーションバイナリインタフェース」など) を定義する
ベンダー固有の公開インタフェースを定義する
ベンダー固有の非公開インタフェースを定義する
オブジェクトの内部実装に対する変更 (ベンダー特定) を定義する
次のバージョン定義命名規約は、定義がどの分類に属するのかを示すために役立ちます。
最初の 3 つの分類は、インタフェース定義を示します。これらの定義は、インタフェースを構成する大域シンボル名とバージョン定義名の関連付けからなります(バージョン定義の作成を参照)。共有オブジェクト内のインタフェースの変更は、しばしば「マイナーリビジョン (minor revision)」と呼ばれます。このため、このタイプのバージョン定義には、マイナー (minor) バージョンナンバーの接尾辞を付けます。これは、ファイル名のメジャー (major) バージョンナンバーの接尾辞をベースとしたものです。
最後の分類は、オブジェクト内の変更を示します。この定義は、ラベルとして機能するバージョン定義からなり、関連するシンボル名はありません。この定義は、ウィーク (weak) バージョン定義と呼ばれます(ウィークバージョン定義の作成を参照)。共有オブジェクト内の実装の変更は、しばしば「マイクロリビジョン (micro revision)」と呼ばれます。このため、このタイプのバージョン定義には、マイクロ (micro) バージョンナンバーの接尾辞を付けます。これは、元になったもののマイナーナンバーをベースとしたものです。
業界標準インタフェースは、この標準を反映するバージョン定義名を使用しなければなりません。ベンダーインタフェースは、そのベンダー固有のバージョン定義名を使用する必要があります。この点で、しばしば利用されるのが、企業の株式銘柄のシンボルです。
非公開バージョン定義は、使用方法が制限されているかまたは保証されていないシンボルを示します。特に、「private」という語を明確に示す必要があります。
バージョン定義を行うと、関連するバージョンシンボル名が必ず作成されます。したがって、一意の名前およびマイナー (minor) / マイクロ (micro) の接尾辞の使用という規則を使用すると、構築されるオブジェクト内でシンボルが衝突する可能性を減らすことができます。
次の定義例は、これらの命名規約の使用方法を示しています。
「System V アプリケーションバイナリインタフェース」標準インタフェースを定義します。
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); |
実際には、この方法でバージョン結合を行う必要はほとんどありません。新しい標準バイナリインタフェースの導入はめったになく、ほとんどのアプリケーションが多くのシンボルをインタフェースファミリから参照します。