プロセスは、dlsym(3DL) を使用して特定のシンボルのアドレスを入手できます。この関数は、ハンドルとシンボルをとり、呼び出し元にそのシンボルのアドレスを戻します。ハンドルは、次の方法でシンボルの検索を指示します。
指定されたオブジェクトの dlopen(3DL) から戻されたハンドルを使用すると、オブジェクトの依存関係ツリーからシンボルを入手できる
値が 0 のパス名の dlopen(3DL) から戻されたハンドルを使用すると、動的実行プログラム、任意の初期設定の依存関係、または RTLD_GLOBAL モードの dlopen(3DL) によって入手されたオブジェクトから、シンボルを入手できる
特別なハンドル RTLD_DEFAULT を使用すると、動的実行プログラム、任意の初期設定の依存関係、または呼び出し元と同じグループに属する dlopen(3DL) によって入手されたオブジェクトから、シンボルを入手できる
最初の例は、最も一般的なものです。アプリケーションは、追加オブジェクトをそのアドレススペースに追加し、さらに dlsym(3DL) を使用して関数またはデータシンボルを配置します。次に、アプリケーションは、これらのシンボルを使用して、新しいオブジェクト内で提供されるサービスを呼び出します。たとえば、次のコードが組み込まれた main.c ファイルを取り上げてみます。
#include <stdio.h> #include <dlfcn.h> main() { void * handle; int * dptr, (* fptr)(); if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) { (void) printf("dlopen: %s\n", dlerror()); exit (1); } if (((fptr = (int (*)())dlsym(handle, "foo")) == NULL) || ((dptr = (int *)dlsym(handle, "bar")) == NULL)) { (void) printf("dlsym: %s\n", dlerror()); exit (1); } return ((*fptr)(*dptr)); } |
シンボル foo と bar は、ファイル foo.so.1 内で検索された後で、このファイルに関連した依存関係が検索されます。次に、関数 foo は、単一の引数 bar によって return() ステートメントの一部として呼び出されます。
アプリケーション prog が、上記のファイル main.c を使用して構築された場合は、その最初の依存関係は次のものになります。
$ ldd prog libdl.so.1 => /usr/lib/libdl.so.1 libc.so.1 => /usr/lib/libc.so.1 |
dlopen(3DL) 内に指定されたファイル名に値 0 がある場合、シンボル foo と bar は、prog、/usr/lib/libdl.so.1、/usr/lib/libc.so.1 の順番で検索されます。
ハンドルがシンボル検索を開始するルートを指示している場合は、この検索メカニズムは、「シンボルの検索」 で説明したものと同じモデルに従います。
要求されたシンボルが配置されていない場合は、dlsym(3DL) は、NULL 値を戻します。この場合、dlerror(3DL) を使用すると、失敗の真の理由を示すことができます。次の例では、アプリケーション prog はシンボル bar を配置できませんでした。
$ prog dlsym: ld.so.1: main: fatal: bar: can't find symbol |
特別なハンドル RTLD_DEFAULT を使用すると、アプリケーションは他のシンボルの存在をテストできます。シンボル検索は、呼び出しオブジェクトを再配置する場合に使用されるものと同じモデルに従います。「デフォルトのシンボル検索モデル」を参照してください。たとえば、アプリケーション prog に次のようなコードフラグメントが組み込まれているとします。
if ((fptr = (int (*)())dlsym(RTLD_DEFAULT, "foo")) != NULL) (*fptr)(); |
この場合 foo は、prog、/usr/lib/libdl.so.1、/usr/lib/libc.so.1 の順番で検索されます。このコードフラグメントが、図 3-1 の例で示すようにファイル B.so.1 に組み込まれていた場合、foo の検索は B.so.1 と C.so.1 でも継続して行われます。
このメカニズムによって、「ウィークシンボル」で説明した定義されていないウィーク参照の代わりに使用できる、パワフルで柔軟性のある代替機能が提供されます。
特別なハンドル RTLD_NEXT を使用すると、アプリケーションは、シンボルの範囲内で次のシンボルを配置できます。たとえば、アプリケーション prog に次のようなコードフラグメントが組み込まれているとします。
if ((fptr = (int (*)())dlsym(RTLD_NEXT, "foo")) == NULL) { (void) printf("dlsym: %s\n", dlerror()); exit (1); } return ((*fptr)()); |
この場合 foo は、prog に関連した共有オブジェクト内で、この場合は /usr/lib/libdl.so.1 の次に /usr/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 (0); } } (void) sprintf(buffer, "malloc: %#x bytes\n", size); (void) write(1, buffer, strlen(buffer)); return ((*fptr)(size)); } |
この共有オブジェクトを、malloc(3C) が常駐するシステムライブラリ /usr/lib/libc.so.1 の間に割り込ませることにより、元の関数が呼び出されて配置が完了する前に、この関数への呼び出しが次のように割り込まれます。
$ 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 を使用することにより、予測可能で有用な割り込みテクニックが使用できます。ただし、このテクニックを汎用オブジェクトの依存関係内で使用する場合には、実際に読み込まれる順番が必ず予測できるとは限らないため、注意が必要です。