リンカーとライブラリ

新しいシンボルの入手

プロセスは、dlsym(3C) を使用して特定のシンボルのアドレスを入手できます。この関数は、ハンドルとシンボルをとり、呼び出し元にそのシンボルのアドレスを戻します。ハンドルは、次の方法でシンボルの検索を指示します。

次に、一般的なケースを示します。この例では、アプリケーションはそのアドレス空間に追加オブジェクトを追加します。続いてアプリケーションは、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));
}

シンボル foobar は、ファイル foo.so.1 内で検索された後で、このファイルに関連した依存関係が検索されます。次に、関数 foo は、単一の引数 bar によって return() ステートメントの一部として呼び出されます。

上記のファイル main.c を使用して構築されたアプリケーション prog には、次のような依存関係があります。


$ ldd prog
        libc.so.1 =>     /lib/libc.so.1

dlopen(3C) で指定されたファイル名の値が 0 の場合、シンボル foobar が、まず 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_DEFAULTRTLD_PROBE を使用すると、ほかのシンボルの有無を確認するためにアプリケーションをテストできます。シンボル検索は、呼び出しオブジェクトを再配置する場合に使用されるものと同じモデルに従います。「デフォルトのシンボル検索モデル」を参照してください。たとえば、アプリケーション prog に次のようなコードフラグメントが組み込まれているとします。


        if ((fptr = (int (*)())dlsym(RTLD_DEFAULT, "foo")) != NULL)
                (*fptr)();

この場合、foo が、まず prog で、次に /lib/libc.so.1 で検索されます。このコードフラグメントが、図 3–1 の例で示すようにファイル B.so.1 に組み込まれていた場合、foo の検索は B.so.1C.so.1 でも、この順に継続して行われます。

このメカニズムによって、「ウィークシンボル」で説明した、定義されていないウィーク参照の代わりに使用できる、堅牢かつ柔軟性のある代替機能が提供されます。

割り込みの使用

特別なハンドル 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 に含まれている場合、fooC.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 を使用することにより、予測可能な割り込みテクニックが使用できます。ただし、このテクニックを汎用オブジェクトの依存関係内で使用する場合には、実際に読み込まれる順番が必ず予測できるとは限らないため、注意が必要です。