リンカーとライブラリ

新しいシンボルの入手

プロセスは、dlsym(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));
}

ここで、シンボル foobar は、ファイル 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 がある場合、シンボル foobar は、prog/usr/lib/libdl.so.1/usr/lib/libc.so.1 の順番で検索されます。

ハンドルがシンボル検索を開始するルートを指示している場合は、この検索メカニズムは、「シンボルの検索」で説明したものと同じモデルに従います。

要求されたシンボルが配置されていない場合は、dlsym(3DL) は、NULL 値を戻します。この場合、dlerror(3DL) を使用すると、失敗の真の理由を示すことができます。次に例を示します。


$ prog
dlsym: ld.so.1: main: fatal: bar: can't find symbol

ここでは、アプリケーション prog は、シンボル bar を配置できませんでした。

機能のテスト

特別なハンドル 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-2 の例で示すようにファイル B.so.1 に組み込まれていた場合、foo の検索は B.so.1C.so.1 でも継続して行われます。

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

割り込み (interposition) の使用

特別なハンドル 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-2 の例で示すように、ファイル 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 を使用することにより、予測可能で有用な割り込みテクニックが使用できます。ただし、このテクニックを汎用オブジェクトの依存関係内で使用する場合には、実際に読み込まれる順番が必ず予測できるとは限らないため、注意が必要です (「依存関係の並べ変え」を参照)。