スタブオブジェクトは共有オブジェクトで、mapfiles からすべて作成されます。また、コードやデータを持ちませんが、本物のオブジェクト同じリンクインタフェースを提供します。スタブオブジェクトは、実行時には使用できません。しかし、スタブオブジェクトに対してアプリケーションを作成できます。スタブオブジェクトによって、実行時に使用される実際のオブジェクト名が提供されます。
スタブオブジェクトの作成時には、リンカーはコマンド行で指定されているオブジェクトとライブラリファイルをすべて無視するため、スタブを作成するためにこれらのファイルが存在している必要はありません。コンパイル手順が省略でき、リンカーの作業が比較的少ないため、スタブオブジェクトはすぐに作成できます。
スタブオブジェクトは、構築時のさまざまな問題点の解決に利用できます。
速度
最新のマシンで、処理を並列化できるバージョンの make ユーティリティーを使用すると、多数のオブジェクトを同時にコンパイルしてリンクできるため、大幅なスピードアップが図れます。しかし、あるオブジェクトがほかのオブジェクトに依存したり、ほかのほぼすべてのオブジェクトが依存する中核となるオブジェクトセットが存在したりするのが一般的です。すべてのオブジェクトが別のオブジェクトで使用される前に作成されるように、構築の順番を付ける必要があります。この順序付けによってボトルネックが生じ、並列処理できる分量が減ったり、コードを作成できる全体の速度が制限されたりします。
複雑さと正確さ
大規模なコードの場合、さまざまなオブジェクト間に大量の依存関係が存在するときがあります。これらのオブジェクトに関する makefiles などの構築の記述が非常に複雑になり、把握と維持が困難になる場合があります。システムの変化に伴って、依存関係が変わる場合があります。これにより、ある makefiles のセットが徐々に不正確になり、競合状態が発生したり、不可解でまれに起こる構築障害に繋がったりします。
依存関係のサイクル
協力し合う共有オブジェクトを作成し、各オブジェクトがリソースを提供して互いに利用し合うようにコードを設計することが望ましい場合もあります。ただし、あるオブジェクトが、そのオブジェクトを使用するオブジェクトの前に作成されなければならない環境では、このようなサイクルはサポートできません。実行時リンカーがこのようなオブジェクト (作成できるとしても) の読み込みと使用に完全に対応していますが、サポートできません。
スタブの共有オブジェクトは、上記の問題点を回避する別のコード作成方法を提供します。ビルドによって作成されるすべての共有オブジェクトに対して、スタブオブジェクトをすぐに作成できます。その後、実際の共有オブジェクトと実行可能ファイルはすべて、リンク時に実際のオブジェクトの代わりを務めるスタブオブジェクトを使用して、平行してどのような順序でも作成できます。実行可能ファイルと実際の共有オブジェクトは保持され、スタブの共有オブジェクトは破棄されます。
スタブオブジェクトは 1 つまたは複数の mapfile から作成され、次の要件を集合的に満たす必要があります。
少なくとも、1 つの mapfile で STUB_OBJECT 指令を指定する必要があります。STUB_OBJECT ディレクティブを参照してください。
オブジェクトへの外部インタフェースを構成する関数とデータのすべてのシンボルは、mapfile 内に明示的に列挙される必要があります。
mapfile はシンボル範囲の縮小 ('*') を使用して、明示的に列挙されていないすべてのシンボルを外部インタフェースから削除する必要があります。SYMBOL_SCOPE および SYMBOL_VERSION ディレクティブを参照してください。
オブジェクトからエクスポートされるすべての大域データには、シンボルのタイプとサイズを指定するために、mapfile に ASSERT シンボル属性が必要です。同じデータを参照するシンボルが複数ある場合、いずれかのシンボルの ASSERT で TYPE と SIZE の属性を指定し、その他では ALIAS 属性を使用して、このプライマリシンボルを参照する必要があります。ASSERT 属性を参照してください。
このような mapfile を使用すると、共有オブジェクトのスタブと実オブジェクトそれぞれの作成時に同じコマンド行を使用できます。–z stub オプションは、スタブオブジェクトのリンク編集に追加され、実オブジェクトのリンク編集から削除されます。
これらの考えを示すため、次のコードで名前が idx5 の共有オブジェクトを実装します。このオブジェクトは 5 つの要素を持つ整数の配列からデータをエクスポートします。各要素は初期化され、ゼロから始まる配列インデックスを格納します。このデータは、大域配列として、また結合が弱い代替の別名データシンボルとして、関数インタフェースを介して公開されます。
$ cat idx5.c int _idx5[5] = { 0, 1, 2, 3, 4 }; #pragma weak idx5 = _idx5 int idx5_func(int index) { if ((index < 0) || (index > 4)) return (-1); return (_idx5[index]); }
mapfile では、この共有オブジェクトで提供されるインタフェースを記述する必要があります。
$ cat mapfile $mapfile_version 2 STUB_OBJECT; SYMBOL_SCOPE { _idx5 { ASSERT { TYPE=data; SIZE=4[5] }; }; idx5 { ASSERT { BINDING=weak; ALIAS=_idx5 }; }; idx5_func; local: *; };
次のメインプログラムは、idx5 共有オブジェクトから利用可能なすべてのインデックス値を出力するために使用されます。
$ cat main.c #include <stdio.h> extern int _idx5[5], idx5[5], idx5_func(int); int main(int argc, char **argv) { int i; for (i = 0; i < 5; i++) (void) printf("[%d] %d %d %d\n", i, _idx5[i], idx5[i], idx5_func(i)); return (0); }
次のコマンドでは、この共有オブジェクトのスタブ版を、stublib という名前のサブディレクトリに作成します。elfdump コマンドは、作成されるオブジェクトがスタブであることを検証するために使用されます。スタブの作成に使用されるコマンドは、–z stub オプションを追加する点、および異なる出力ファイル名を使用する点だけが、実オブジェクトのコマンドと異なります。これは、スタブ作成が既存コードに容易に追加できることを示します。
$ cc -Kpic -G -M mapfile -h libidx5.so.1 idx5.c -o stublib/libidx5.so.1 -zstub $ ln -s libidx5.so.1 stublib/libidx5.so $ elfdump -d stublib/libidx5.so | grep STUB [11] FLAGS_1 0x4000000 [ STUB ]
これで、実際の共有オブジェクトの代わりになるスタブオブジェクトを使用して、メインプログラムを構築できるようになりました。スタブオブジェクトは、実行時に実オブジェクトを検索する「実行パス」を設定します。しかし、実オブジェクトはまだ作成されていないため、このプログラムはまだ動作しません。システムがスタブオブジェクトを読み込もうとする試みは拒否されます。実行時リンカーは、実オブジェクトにある実際のコードとデータがスタブオブジェクトにないために、実行できないことをわかっているためです。
$ cc main.c -L stublib -R '$ORIGIN/lib' -lidx5 -lc $ ./a.out ld.so.1: a.out: fatal: libidx5.so.1: open failed: No such file or directory Killed $ LD_PRELOAD=stublib/libidx5.so.1 ./a.out ld.so.1: a.out: fatal: stublib/libidx5.so.1: stub shared object \ cannot be used at runtime Killed
実オブジェクトの作成には、スタブオブジェクトを作成するために使用したコマンドと同じものを使用します。–z stub オプションは省かれ、実際の出力ファイルのパスが指定されます。
$ cc -Kpic -G -M mapfile -h libidx5.so.1 idx5.c -o lib/libidx5.so.1
実オブジェクトが lib サブディレクトリに作成されたあとは、このプログラムを実行できます。
$ ./a.out [0] 0 0 0 [1] 1 1 1 [2] 2 2 2 [3] 3 3 3 [4] 4 4 4
ライブラリは変化し、元の機能が望ましくなくなることがあります。新しい機能が追加され、古い機能が廃止されたとみなされることはよくあります。下位互換性が重要な場合は、既存のオブジェクトのためにこのような古い機能をライブラリに保持しておく必要があります。ただし、これらの機能を新たに使用することのないように設定できます。このポリシーを実施するには、スタブオブジェクトを使用します。mapfile STUB_ELIMINATE フラグを使用して、実オブジェクトに残しながらスタブオブジェクトから削除する必要のあるオブジェクトの機能やデータにマークを付けます。これによって、スタブオブジェクトにリンクする新しいコードは、これらの廃止された項目を使用できなくなり、推奨のインタフェースを使用するように書き換えられます。実オブジェクトにはまだこれらの項目が含まれているので、既存のオブジェクトはこれらを使用できます。
前のセクションの libidx5 の例には、このことが示されています。このライブラリは、オブジェクトから大域データをエクスポートする方法を示しています。ただし、大域データのエクスポートは動的リンクを複雑にするので、行わないことをお勧めします。通常は、libidx5 によって提供される idx5_func() 関数など、このようなデータにアクセスする関数を用意したほうが適切な設計になります。この例の続きでは、既存のプログラムのために実オブジェクト内で古いインタフェースを提供しながら、スタブにリンクする新しいコードで大域データを使用できないようにするために、STUB_ELIMINATE を使用できます。
mapfile は、STUB_ELIMINATE を 2 つの大域データシンボルに適用するように書き換えられます。STUB_ELIMINATE を大域データに適用した場合、データサイズを指定する ASSERT 指令を用意する必要がなくなるという利点があります。この例では、ASSERT がコメントアウトされています。実際の mapfile では、完全に省略されていることもあります。
$ cat better_mapfile $mapfile_version 2 STUB_OBJECT; SYMBOL_SCOPE { _idx5 { FLAGS=STUB_ELIMINATE; #ASSERT { TYPE=data; SIZE=4[5] }; }; idx5 { FLAGS=STUB_ELIMINATE; #ASSERT { BINDING=weak; ALIAS=_idx5 }; }; idx5_func; local: *; };
新しいバージョンのテストプログラムだけが機能インタフェースを使用します。
$ cat better_main.c #include <stdio.h> extern int idx5_func(int); int main(int argc, char **argv) { int i; for (i = 0; i < 5; i++) (void) printf("[%d] %d\n", i, idx5_func(i)); return (0); }
古いテストプログラムは保存され、スタブオブジェクトは新しい mapfile を使用して再構築され、テストプログラムは再構築され、STUB_ELIMINATE を採用した新しいスタブオブジェクトに対してリンクします。
$ cp a.out original_a.out $ cc -Kpic -G -M better_mapfile -h libidx5.so.1 idx5.c -o stublib/libidx5.so.1 -zstub $ cc better_main.c -o better_a.out -L stublib -R '$ORIGIN/lib' -lidx5 -lc $ ./better_a.out [0] 0 [1] 1 [2] 2 [3] 3 [4] 4
スタブライブラリには必要な大域データシンボルが欠如しているので、元のテストプログラムはもはや構築できません。ただし、まだ実際のライブラリ大域がデータシンボルを提供するので、それらを使用していた事前に存在するバイナリは機能し続けます。
$ cc main.c -L stublib -R '$ORIGIN/lib' -lidx5 -lc Undefined first referenced symbol in file idx5 main.o _idx5 main.o ld: fatal: symbol referencing errors $ ./original_a.out [0] 0 0 0 [1] 1 1 1 [2] 2 2 2 [3] 3 3 3 [4] 4 4 4