ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
リンカーとライブラリ Oracle Solaris 11 Information Library (日本語) |
アプリケーションのリンク編集中に指定された依存関係は、プロセスの初期設定中に実行時リンカーによって処理されます。このメカニズムに加えて、アプリケーションは、追加オブジェクトと結合することにより、その実行中にアドレススペースを拡張できます。アプリケーションは、アプリケーションの標準的な依存関係の処理に使用される、実行時リンカーの同じサービスを実際に使用します。
遅延オブジェクトの結合処理には、いくつかの利点があります。
アプリケーションの初期設定中ではなく、オブジェクトが要求された時点でオブジェクトを処理することにより、起動時間を大幅に削減できます。アプリケーションの特定の実行中に、オブジェクトにより提供されるサービスが必要でない場合、オブジェクトは要求されません。このような状態は、ヘルプやデバッグ情報を提供するオブジェクトに対して発生する可能性があります。
アプリケーションは、ネットワーキングプロトコルなどの、必要なサービスによって決まる、いくつかの異なるオブジェクト間で選択されます。
実行時にオブジェクトに追加されたプロセスのアドレススペースは、使用後には解放されます。
アプリケーションは、次の典型的な手順を使用して、追加の共有オブジェクトにアクセスできます。
共有オブジェクトは、dlopen(3C) を使用して実行中のアプリケーションのアドレススペースに配置され、追加されます。この共有オブジェクトの依存関係は、この時点で配置されて追加されます。
追加された共有オブジェクトとその依存関係は、再配置されます。これらのオブジェクト内の初期設定セクションが呼び出されます。
アプリケーションは、追加されたオブジェクト内のシンボルを、dlsym(3C) を使用して配置します。次に、アプリケーションはデータを参照するか、またはこの新しいシンボルによって定義された関数を呼び出します。
オブジェクトによってアプリケーションが終了したあとで、dlclose(3C) を使用してアドレススペースを解放できます。解放されたオブジェクト内に存在する終了セクションは、すべてこの時点で呼び出されます。
実行時リンカーのインタフェースルーチンを使用した結果発生したエラー状態は、dlerror(3C) を使用して表示できます。
実行時リンカーのサービスは、ヘッダーファイル dlfcn.h 内に定義されており、共有オブジェクト libc.so.1 経由でアプリケーションから使用できます。次の例では、ファイル main.c は、ルーチンのいずれの dlopen(3C) ファミリでも参照でき、アプリケーション prog は、実行時にこれらのルーチンと結合できます。
$ cc -o prog main.c
注 - Oracle Solaris OS の以前のリリースでは、動的リンクインタフェースは、共有オブジェクト libdl.so.1 によって使用可能にされていました。libdl.so.1 は既存の依存関係をサポートするために今も使用できます。ただし、libdl.so.1 から提供されていた動的リンクインタフェースは、現在は libc.so.1 から使用できるようになりました。このため、-ldl によるリンクは必要なくなりました。
追加オブジェクトは、dlopen(3C) を使用して、実行プロセスのアドレススペースに追加できます。この関数は、引数としてファイル名と結合モードを入手し、アプリケーションにハンドルを戻します。このハンドルを使用すると、アプリケーションは、dlsym(3C) を使用することによってシンボルを配置できます。
パス名が、単純ファイル名で指定されている (名前の中に「/」が組み込まれていない) 場合、実行時リンカーは一連の規則を使用して、適切なパス名を生成します。「/」が組み込まれたパス名は、そのまま使用されます。
これらの検索パスの規則は、最初の依存関係の配置に使用された規則と全く同じものです。「実行時リンカーが検索するディレクトリ」を参照してください。たとえば、ファイル main.c には、次のようなコードフラグメントが組み込まれているとします。
#include <stdio.h> #include <dlfcn.h> int main(int argc, char **argv) { void *handle; ..... if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) { (void) printf("dlopen: %s\n", dlerror()); return (1); } .....
実行時リンカーは、共有オブジェクト foo.so.1 を検索するために、プロセスの初期設定時に存在する任意の LD_LIBRARY_PATH 定義を使用します。続いて、実行時リンカーは prog のリンク編集時に指定された「実行パス」を使用します。最後に、実行時リンカーは 32 ビットオブジェクトの場合は /lib と /usr/lib、64 ビットオブジェクトの場合は /lib/64 と /usr/lib/64 のデフォルト位置を使用します。
パス名が次のように指定されているとします。
if ((handle = dlopen("./foo.so.1", RTLD_LAZY)) == NULL) {
実行時リンカーは、プロセスの現在のカレントディレクトリ内でこのファイルだけを検索します。
注 - dlopen(3C) を使用して指定された共有オブジェクトは、「そのバージョンのファイル名」で参照することをお勧めします。バージョン管理の詳細については、「バージョン管理ファイル名の管理」を参照してください。
必要なオブジェクトを配置できない場合は、dlopen(3C) によって NULL ハンドルが戻されます。この場合、dlerror(3C) を使用すると、失敗した真の理由を表示できます。次に例を示します。
$ cc -o prog main.c $ prog dlopen: ld.so.1: prog: fatal: foo.so.1: open failed: No such \ file or directory
dlopen(3C) によって追加されたオブジェクトに、ほかのオブジェクトに依存する関係がある場合、その依存関係もプロセスのアドレススペースに配置されます。このプロセスは、指定されたオブジェクトの依存関係がすべて読み込まれるまで継続されます。この依存関係のツリーを「グループ」と呼びます。
dlopen(3C) によって指定されたオブジェクト、またはその依存関係が、すでにプロセスイメージの一部である場合は、そのオブジェクトはこれ以上処理されません。この場合でも有効なハンドルは、アプリケーションに戻されます。このメカニズムにより、同じオブジェクトが複数回読み込まれることを防ぐことができます。また、このメカニズムを使用すると、アプリケーションは専用のハンドルを入手できます。たとえば、前述の例で main.c に次の dlopen() 呼び出しが含まれている場合があります。
if ((handle = dlopen(0, RTLD_LAZY)) == NULL) {
この dlopen(3C) から返されたハンドルを使用すれば、アプリケーション自身の内部、プロセスの初期設定中に読み込まれたすべての依存関係内、またはプロセスのアドレススペースに追加されたすべてのオブジェクト内で、シンボル検索を行えます。それには、dlopen(3C) で RTLD_GLOBAL フラグを指定します。
実行時リンカーは、オブジェクトを検出して読み込んだあと、各オブジェクトを処理し、必要な再配置を実行します。また、dlopen(3C) を使用してプロセスのアドレススペースに配置されたオブジェクトは同じ方法で再配置する必要があります。
単純なアプリケーションの場合には、このプロセスはそれほど重要な意味を持ちません。しかし、多くのオブジェクトを含む多数の dlopen(3C) 呼び出しと、共通の依存関係も伴う複雑なアプリケーションを所有するユーザーにとって、このプロセスは非常に重要です。
再配置は、その実行タイミングに基づいて分類できます。実行時リンカーのデフォルトの動作では、初期設定時に即時参照の再配置がすべて処理され、プロセスの実行時に遅延参照がすべて処理されます。後者の処理は通常、遅延結合と呼ばれます。
モードが RTLD_LAZY として定義されている時に dlopen(3C) を使って追加されたすべてのオブジェクトに対して、これと同じメカニズムが適用されます。代替手段は、オブジェクトが追加されるときにただちに実行されるオブジェクトのすべての再配置を要求することです。RTLD_NOW のモードを使用するか、またはリンカーの -z now オプションを使用して構築した際のオブジェクトにこの要件を記録できます。この再配置の必要条件は、オープン状態のオブジェクトの依存関係に伝達されます。
また、再配置は、非シンボリックおよびシンボリックにも分類できます。このセクションの後半では、シンボル再配置がいつ発生するかに関係なく、この再配置に関連した問題について、シンボル検索の詳細に焦点をあてて説明します。
dlopen(3C) によって取得したオブジェクトが大域シンボルを参照する場合は、実行時リンカーは、プロセスを作成するオブジェクトのプールからこのシンボルを配置する必要があります。直接結合がない場合は、dlopen() によって入手されたオブジェクトには、デフォルトのシンボル検索モデルが適用されます。ただし、プロセスを作成するオブジェクトの属性と結合される dlopen() のモードは、代わりのシンボル検索モデルに提供されます。
直接結合を指定されたオブジェクトでは、それに対応する依存関係から直接、シンボルが検索されます。ただし、この後で述べるすべての属性はそのまま有効です。第 9 章直接結合を参照してください。
デフォルトにより、dlopen(3C) を使用して入手したオブジェクトには、「ワールド」シンボル検索範囲と「ローカル」シンボル可視性が割り当てられます。「デフォルトのシンボル検索モデル」では、このデフォルトモデルを使用して、典型的なオブジェクトグループのインタラクションについて説明しています。「大域オブジェクトの定義」、「グループの分離」、および 「オブジェクト階層」では、デフォルトのシンボル検索モデルの展開に dlopen(3C) モードとファイル属性を使用する例を示しています。
基本的な dlopen(3C) によって追加された各オブジェクトでは、実行時リンカーは、最初に動的実行可能ファイル内でシンボルを検索します。次に実行時リンカーは、プロセスの初期設定中に提供されたそれぞれのオブジェクト内を検索します。シンボルが見つからない場合、実行時リンカーは検索を続けます。実行時リンカーは次に dlopen(3C) によって取得されたオブジェクトおよびそのオブジェクトと依存関係にあるオブジェクトを検索します。
デフォルトのシンボル検索モデルは、遅延読み込み環境の遷移も行います。現在読み込まれているオブジェクト内でシンボルが見つからない場合は、そのシンボルを特定するために、保留となっている遅延読み込みオブジェクトが処理されます。この読み込みによって、依存関係を完全には定義していないオブジェクトを補います。ただし、これにより遅延読み込みの利点が失われることがあります。
次の例の動的実行可能プログラム prog と共有オブジェクト B.so.1 には、依存関係が付いています。
$ ldd prog A.so.1 => ./A.so.1 $ ldd B.so.1 C.so.1 => ./C.so.1
prog が、dlopen(3C) を使用して共有オブジェクト B.so.1 を入手した場合、共有オブジェクト B.so.1 と C.so.1 の再配置に必要なシンボルが、最初に prog 内で検索され、A.so.1、B.so.1、C.so.1 の順に検索されます。このような単純なケースでは、dlopen(3C) によって入手された共有オブジェクトは、アプリケーションの元のリンク編集の末尾に追加されたと考えます。たとえば、上記のオブジェクトの参照を図示すると、次のようになります。
図 3-1 単一の dlopen() 要求
dlopen(3C) から入手されたオブジェクトによって要求されたシンボル検索は、影付きのブロックで示しています。このシンボル検索は、動的実行プログラム prog から、最後の共有オブジェクト C.so.1 へと進みます。
このシンボル検索は、読み込まれたオブジェクトに割り当てられた属性によって確立されます。動的実行可能ファイルとそれと同時に読み込まれたすべての依存関係には、大域シンボル可視性が割り当てられ、新しいオブジェクトにはワールドシンボルの検索範囲が割り当てられることを思い出してください。これによって、新しいオブジェクトは元のオブジェクト内を調べてシンボルを検索できます。また、新しいオブジェクトは、固有のグループを形成し、このグループ内では、各オブジェクトは局所シンボル可視性を持ちます。そのため、グループ内の各オブジェクトは、ほかのグループメンバー内でシンボルを検索できます。
これらの新しいオブジェクトは、アプリケーションまたはアプリケーションの最初の依存関係によって要求される、通常のシンボル検索には影響を与えません。たとえば、上記の dlopen(3C) が実行された後で、A.so.1 に関数再配置が必要な場合、実行時リンカーの再配置シンボルの通常の検索は、順に prog と A.so.1 で実施されます。B.so.1 または C.so.1 は検索されません。
このシンボル検索は、読み込まれたときにオブジェクトに割り当てられた属性によって実行されます。ワールドシンボルの検索範囲が、動的実行可能プログラムとこれとともに読み込まれた依存関係に割り当てられます。この検索範囲では、局所シンボル可視性だけを提供する新しいオブジェクト内を検索できません。
これらのシンボル検索とシンボル可視性の属性は、オブジェクト間の関係を保持します。これらの関係は、そのプロセスのアドレススペースへの投入とオブジェクト間の依存の関係に基づいています。指定された dlopen(3C) に関連したオブジェクトを固有のグループに割り当てることにより、同じ dlopen(3C) と関連したオブジェクトだけが、グループ内のシンボルと、関連する依存関係の中の検索ができます。
このオブジェクト間の関係を定義するという概念は、複数の dlopen(3C) を実行するアプリケーション内では、より明確になります。たとえば、共有オブジェクト D.so.1 に次の依存関係があるとします。
$ ldd D.so.1 E.so.1 => ./E.so.1
このとき、prog アプリケーションが、共有オブジェクト B.so.1 に加えてこの共有オブジェクトも dlopen(3C) を使って読み込んだとします。次の図は、オブジェクト間のシンボル検索の関係を示しています。
図 3-2 複数の dlopen() 要求
B.so.1 と D.so.1 の両方にシンボル foo の定義が組み込まれ、C.so.1 と E.so.1 にこのシンボルを必要とする再配置が組み込まれているとします。固有のグループに対するオブジェクトの関係によって、C.so.1 は B.so.1 の定義に結合され、E.so.1 は D.so.1 の定義に結合されます。このメカニズムは、dlopen(3C) への複数の呼び出しにより入手されたオブジェクトのもっとも直感的な結合を提供するためのものです。
オブジェクトが、前述した処理の進行の中で使用される場合、それぞれの dlopen(3C) が実施された順序は、結果として発生するシンボル結合には影響しません。ただし、複数のオブジェクトに共通の依存関係がある場合は、結果の結び付きは、dlopen(3C) 呼び出しが実行された順序の影響を受けます。
次に、同じ共通依存関係を持つ共有オブジェクト O.so.1 と P.so.1 の例を示します。
$ ldd O.so.1 Z.so.1 => ./Z.so.1 $ ldd P.so.1 Z.so.1 => ./Z.so.1
この例では、prog アプリケーションは、各共有オブジェクトに dlopen(3C) を使用しています。共有オブジェクト Z.so.1 が、O.so.1 と P.so.1 両方の共通依存関係であるため、Z.so.1 は 2 つの dlopen(3C) 呼び出しに関連する両方のグループに割り当てられます。この依存関係を次の図に示します。
図 3-3 共通依存関係を伴う複数の dlopen() 要求
この結果、O.so.1 と P.so.1 の両方がシンボルの検索に Z.so.1 を使用できます。ここで重要なのは、dlopen(3C)の順序に限って言えば、Z.so.1 も O.so.1 と P.so.1 の両方の中でシンボルを検索できることです。
そのため、O.so.1 と P.so.1 の両方に、Z.so.1 の再配置に必要なシンボル foo の定義が組み込まれている場合、実際に発生する結び付きを予期することはできません。それは、この結び付きが dlopen(3C) 呼び出しの順序の影響を受けるからです。シンボル foo の機能が、シンボルが定義されている 2 つの共有オブジェクト間で異なる場合、Z.so.1 でコードを実行したすべての結果は、アプリケーションの dlopen(3C) の順序によって異なる可能性があります。
dlopen(3C) で取得されるオブジェクトにデフォルトで割り当てられる「ローカル」シンボル可視性を「大域」に拡張するには、モード引数に RTLD_GLOBAL フラグを指定します。このモードでは、dlopen(3C) によって入手されたオブジェクトは、シンボルを配置するための、ワールドシンボルの検索範囲が指定されたほかのオブジェクトによって使用することができます。
また、RTLD_GLOBAL フラグが指定された dlopen(3C) によって入手されたオブジェクトは、dlopen() (値 0 のパス名を指定) を使用したシンボル検索にも使用できます。
注 - グループのメンバーが、局所シンボルの可視性を定義しており、かつ大域シンボルの可視性を定義するほかのグループによって参照されている場合、オブジェクトの可視性は局所と大域の両方を連結したものになります。この後大域グループの参照が削除されても、この格上げされた属性はそのまま残ります。
dlopen(3C) で取得されるオブジェクトにデフォルトで割り当てられる「ワールド」シンボル検索範囲を「グループ」に縮小するには、モード引数に RTLD_GROUP フラグを指定します。このモードでは、dlopen(3C) によって入手されたオブジェクトは、そのオブジェクト固有のグループ内でしかシンボルの検索ができません。
リンカーの -B group オプションを使用して構築したオブジェクトには、グループのシンボル検索範囲を割り当てることができます。
注 - グループのメンバーがグループの検索要件を定義しており、かつワールド検索要件を定義する別のグループによって参照されている場合、オブジェクトの検索要件はグループとワールドの両方を連結したものになります。この後ワールドグループの参照が削除されても、この格上げされた属性はそのまま残ります。
最初のオブジェクトが dlopen(3C) によって入手され、dlopen() を使用して 2 番目のオブジェクトを開いた場合、両方のオブジェクトは同じ固有のグループに割り当てられます。これにより、オブジェクトが互いにシンボルを配置し合うことを防ぐことができます。
実装の中には、最初のオブジェクトの場合、シンボルを 2 番目のオブジェクトの再配置用にエクスポートする必要がある場合もあります。この必要条件は、次の 2 つのメカニズムのいずれかによって満たすことができます。
最初のオブジェクトを 2 番目のオブジェクトの明示的な依存関係にする。
2 番目のオブジェクトに対する dlopen(3C)で RTLD_PARENT モードフラグを使用する。
最初のオブジェクトを 2 番目のオブジェクトの明示的な依存関係にした場合、これは 2 番目のオブジェクトのグループにも割り当てられます。そのため、最初のオブジェクトは、2 番目のオブジェクトの再配置に必要なシンボルも提供できます。
多くのオブジェクトが dlopen(3C) を使って 2 番目のオブジェクトを開くことができ、かつそれらの初期オブジェクトが 2 番目のオブジェクトの再配置を満たすために同じシンボルをエクスポートしなければならない場合、その 2 番目のオブジェクトに明示的な依存関係を割り当てることはできません。この場合、2 番目のオブジェクトの dlopen(3C) モードは、RTLD_PARENT フラグを使用して補強できます。このフラグによって、2 番目のオブジェクトのグループが、明示的な依存関係が伝達されたのと同じ方法で、最初のオブジェクトに伝達されます。
これら 2 つの手法の間には、小さな相違点が 1 つ存在しています。明示的な依存関係を指定する場合、その依存関係そのものは、2 番目のオブジェクトの dlopen(3C) 依存関係ツリーの一部になるため、dlsym(3C) を使用したシンボル検索が可能になります。RTLD_PARENT を使用して 2 番目のオブジェクトを入手する場合、最初のオブジェクトは、dlsym(3C) を使用したシンボルの検索に使用できるようにはなりません。
「大域」シンボル可視性を持つ初期オブジェクトが dlopen(3C) を使って 2 番目のオブジェクトを取得する場合、RTLD_PARENT モードは冗長かつ無害になります。このような状態は、dlopen(3C) がアプリケーションから呼び出されたとき、またはアプリケーションの中の依存関係の 1 つから呼び出されたときに多く発生します。
プロセスは、dlsym(3C) を使用して特定のシンボルのアドレスを入手できます。この関数は、ハンドルとシンボルをとり、呼び出し元にそのシンボルのアドレスを戻します。ハンドルは、次の方法でシンボルの検索を指示します。
指定されたオブジェクトのハンドルが dlopen(3C) から返される。このハンドルを使用すると、指定したオブジェクトとその依存ツリーを構成するオブジェクト群からシンボルを入手できます。RTLD_FIRST モードを使用して戻されたハンドルの場合は、指定したオブジェクトだけからシンボルを入手できます。
値が 0 のパス名のハンドルが dlopen(3C) から返される。このハンドルを使用すると、関連づけられたリンクマップの開始オブジェクトと、その依存ツリーを構成するオブジェクト群から、シンボルを入手できます。通常、開始オブジェクトは動的実行可能ファイルです。このハンドルを使用すると、関連づけられたリンクマップ上の、dlopen(3C) で RTLD_GLOBAL モードを使用して読み込まれたすべてのオブジェクトからも、シンボルを入手できます。RTLD_FIRST モードを使用して戻されたハンドルの場合は、関連づけられたリンクマップ上の開始オブジェクトだけからシンボルを入手できます。
特別なハンドル RTLD_DEFAULT と RTLD_PROBE を使えば、関連付けられたリンクマップの開始元オブジェクトやその依存関係ツリーを定義するオブジェクトから、シンボルを取得できる。このハンドルを使用すると、呼び出し元と同じグループに属する、dlopen(3C) を使用して読み込まれたオブジェクトからも、シンボルを入手できます。RTLD_DEFAULT または RTLD_PROBE の使用は、呼び出し側のオブジェクトからのシンボル再配置の解決に使用するのと同じモデルに従います。
特別なハンドル RTLD_NEXT を使えば、呼び出し元のリンクマップリスト上に存在する次の関連オブジェクトから、シンボルを取得できる。
次に、一般的なケースを示します。この例では、アプリケーションはそのアドレス空間に追加オブジェクトを追加します。続いてアプリケーションは、dlsym(3C) を使用して関数シンボルまたはデータシンボルを見つけます。次に、アプリケーションは、これらのシンボルを使用して、これらの新しいオブジェクト内で提供されるサービスを呼び出します。ファイル main.c には、次のコードが含まれます。
#include <stdio.h> #include <dlfcn.h> int main() { void *handle; int *dptr, (*fptr)(); if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) { (void) printf("dlopen: %s\n", dlerror()); return (1); } if (((fptr = (int (*)())dlsym(handle, "foo")) == NULL) || ((dptr = (int *)dlsym(handle, "bar")) == NULL)) { (void) printf("dlsym: %s\n", dlerror()); return (1); } return ((*fptr)(*dptr)); }
シンボル foo と bar は、ファイル foo.so.1 内で検索された後で、このファイルに関連した依存関係が検索されます。次に、関数 foo は、単一の引数 bar によって return() ステートメントの一部として呼び出されます。
上記のファイル main.c を使用して構築されたアプリケーション prog には、次のような依存関係があります。
$ ldd prog libc.so.1 => /lib/libc.so.1
dlopen(3C) で指定されたファイル名の値が 0 の場合、シンボル foo と bar が、まず prog で検索され、次に /lib/libc.so.1 で検索されます。
ハンドルは、シンボル検索を開始するルートを示します。このルートから、検索メカニズムは 「再配置シンボルの検索」で説明されているモデルと同じモデルに従います。
要求されたシンボルが配置されていない場合は、dlsym(3C) は、NULL 値を戻します。この場合、dlerror(3C) を使用すると、失敗した真の理由を表示できます。次の例では、アプリケーション prog はシンボル bar を配置できません。
$ prog dlsym: ld.so.1: main: fatal: bar: can't find symbol
特別なハンドル RTLD_DEFAULT と、RTLD_PROBE を使用すると、シンボルの有無を確認するためにアプリケーションをテストできます。
RTLD_DEFAULT ハンドルは、実行時リンカーで使用される規則と同じものを使用して、呼び出し元オブジェクトからのすべての参照を解決します。「デフォルトのシンボル検索モデル」を参照してください。このモデルの 2 つの特徴に注意をしてください。
動的実行可能ファイルからの同じシンボル参照に一致するシンボル参照は、実行可能ファイルからの参照に関連するプロシージャーリンクテーブルのエントリに結合されます。「プロシージャーのリンクテーブル (プロセッサ固有)」を参照してください。動的リンクのこの動作によって、プロセス内のすべてのコンポーネントが、ある関数に対して 1 つのアドレスを参照することが保証されます。
現在プロセス内に読み込まれているオブジェクトの中でシンボルが検出できないと、遅延読み込みのフォールバックが起動されます。このフォールバックは、読み込まれる動的オブジェクトごとに繰り返され、保留中の遅延読み込み可能オブジェクトを読み込んでシンボルの解決を試みます。このモデルは、依存関係を完全には定義していなかったオブジェクトを補います。ただし、これにより遅延読み込みのメリットが損なわれることがあります。再配置シンボルが見つからない場合に、不必要なオブジェクトが読み込まれたり、すべての遅延読み込み可能オブジェクトが完全に読み込まれたりする可能性があります。
RTLD_PROBE は RTLD_DEFAULT と同様のモデルに従いますが、RTLD_DEFAULT で説明した 2 つの点が異なります。RTLD_PROBE は明示的なシンボル定義に結合するだけであり、実行可能ファイル内のプロシージャーリンクテーブルのエントリに結合されません。また、RTLD_PROBE では完全な遅延読み込みフォールバックは起動されません。既存プロセス内でシンボルの有無を検出するには、RTLD_PROBE フラグを使用するのが最適です。
RTLD_DEFAULT と RTLD_PROBE はともに明示的な遅延読み込みを起動できます。オブジェクトは関数を参照でき、その参照は遅延読み込み可能な依存関係を介して確立できます。この関数を呼び出す前に、RTLD_DEFAULT または RTLD_PROBE を使用すると関数の有無をテストできます。オブジェクトはこの関数を参照しているため、関連する遅延依存関係を読み込む試みが最初に行われます。次に、RTLD_DEFAULT と RTLD_PROBE の規則に従って関数に結合されます。次の例では、RTLD_PROBE の呼び出しを使用して、遅延読み込みのトリガーと、依存関係が存在する場合に、読み込まれた依存関係に結合します。
void foo() { if (dlsym(RTLD_PROBE, "foo1")) { foo1(arg1); foo2(arg2); .... }
機能性をテストするモデルを、堅牢で柔軟なモデルにするには、関連する遅延依存関係に明示的に deferred とタグ付けしてください。「dlopen() の代替手段の提供」を参照してください。このタグ付けによって、実行時に遅延依存関係を変更する手段も提供されます。
「ウィークシンボル」で説明したように、RTLD_DEFAULT または RTLD_PROBE を使用すると、未定義のウィーク参照の使用に代わる、より堅牢な手段が提供されます。
特別なハンドル RTLD_NEXT を使用すると、アプリケーションは、シンボルの範囲内で次のシンボルの場所を見つけることができます。たとえば、アプリケーション prog に次のようなコードフラグメントが組み込まれているとします。
if ((fptr = (int (*)())dlsym(RTLD_NEXT, "foo")) == NULL) { (void) printf("dlsym: %s\n", dlerror()); return (1); } return ((*fptr)());
この場合、foo は、prog に関連する共有オブジェクト (この例では /lib/libc.so.1) 内で検索されます。このコード部分が図 3-1 に示されている例からファイル B.so.1 に含まれている場合、foo は C.so.1 でのみ検索されます。
RTLD_NEXT を使用することによって、シンボル割り込みを活用できます。たとえば、オブジェクト内の関数は、オブジェクトの前に付けて割り込みでき、これにより、元の関数の処理を補強できます。たとえば、次のコードフラグメントを共有オブジェクト malloc.so.1 内に配置します。
#include <sys/types.h> #include <dlfcn.h> #include <stdio.h> void * malloc(size_t size) { static void *(*fptr)() = 0; char buffer[50]; if (fptr == 0) { fptr = (void *(*)())dlsym(RTLD_NEXT, "malloc"); if (fptr == NULL) { (void) printf("dlopen: %s\n", dlerror()); return (NULL); } } (void) sprintf(buffer, "malloc: %#x bytes\n", size); (void) write(1, buffer, strlen(buffer)); return ((*fptr)(size)); }
malloc.so.1 は、malloc(3C) が通常存在するシステムライブラリ /lib/libc.so.1 の前に割り込ませることができます。こうすれば、malloc() に対するすべての呼び出しは、本来の関数が呼ばれて割り当てを行う前に、割り込まれます。
$ cc -o malloc.so.1 -G -K pic malloc.c $ cc -o prog file1.o file2.o ..... -R. malloc.so.1 $ prog malloc: 0x32 bytes malloc: 0x14 bytes ..........
あるいは、次のコマンドを使っても、上記と同じ割り込みを実行できます。
$ cc -o malloc.so.1 -G -K pic malloc.c $ cc -o prog main.c $ LD_PRELOAD=./malloc.so.1 prog malloc: 0x32 bytes malloc: 0x14 bytes ..........
注 - 割り込みテクニックを使用する場合、反復する可能性がある処理には注意が必要です。前術の例では、printf(3C) を直接使用する代わりに sprintf(3C) を使用して診断メッセージの書式設定を行なっていますが、これは、printf(3C) が使用する可能性のある malloc(3C) に起因する再帰を回避するためです。
動的実行可能プログラムまたはあらかじめ読み込まれたオブジェクト内で RTLD_NEXT を使用することにより、予測可能な割り込みテクニックが使用できます。ただし、このテクニックを汎用オブジェクトの依存関係内で使用する場合には、実際に読み込まれる順番が必ず予測できるとは限らないため、注意が必要です。