プロセスは、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() の代替手段の提供を参照してください。このタグ付けによって、実行時に遅延依存関係を変更する手段も提供されます。
Weak Symbolsで説明したように、RTLD_DEFAULT または ウィークシンボル を使用すると、未定義のウィーク参照の使用に代わる、より堅牢な手段が提供されます。
特別なハンドル 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) 内で検索されます。このコード部分がFigure 3–1 に示されている例のファイル Figure 3–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 ....
動的実行可能プログラムまたはあらかじめ読み込まれたオブジェクト内で RTLD_NEXT を使用することにより、予測可能な割り込みテクニックが使用できます。ただし、このテクニックを汎用オブジェクトの依存関係内で使用する場合には、実際に読み込まれる順番が必ず予測できるとは限らないため、注意が必要です。