DLOPEN(3)

名前
dlclose, dlerror, dlopen, dlsym - 動的リンクを行うローダへの プログラミングインターフェース

書式
#include <dlfcn.h>

void *dlopen(const char *filename, int flag);

char *dlerror(void);

void *dlsym(void *handle, const char *symbol);

int dlclose(void *handle);

-ldl でリンクする。

説明
dlopen(), dlsym(), dlclose(), dlerror() の 4つの関数は、動的リンク (dynamic linking) を行うローダへの インタフェースを実装したものである。

  dlerror()
関数 dlerror() は、前回 dlerror() が呼び出された後に、 dlopen(), dlsym(), dlclose() のいずれかで最後に発生したエラーについての説明メッセージを返す。 初期化後または前回呼び出された後で、エラーが発生していなければ NULL を返す。

  dlopen()
関数 dlopen() は、NULL 終端された文字列 filename で指定されたファイル名の動的ライブラリ (dynamic library) をロードし、 その動的ライブラリへの内部「ハンドル」を返す。 filename が NULL の場合、メイン・プログラムへのハンドルが返される。 filename がスラッシュ ("/") を含む場合、(相対か絶対かの)パス名として解釈される。 それ以外の場合、動的リンカは以下の手順でライブラリを検索する (詳細は ld.so(8) を参照):

o   (ELF のみ) 呼び出し元プログラムの実行ファイルに DT_RPATH タグが含まれており、 DT_RUNPATH タグが含まれていない場合、DT_RPATH タグに書かれている ディレクトリ・リストを検索する。

o   プログラムの開始時に環境変数 LD_LIBRARY_PATH にコロン区切りのディレクトリのリストが定義されていれば、 この環境変数に定義されたディレクトリが検索される (セキュリティ上の理由で、この変数は set-UID や set-GID された プログラムの場合は無視される)。

o   (ELF のみ) 呼び出し元プログラムの実行ファイルに DT_RUNPATH タグが含まれて いる場合、そのタグに書かれているディレクトリ・リストを検索する。

o   キャッシュファイル /etc/ld.so.cache の中に filename のエントリが入っているかをチェックする (/etc/ld.so.cache は ldconfig(8) によって管理されている)。

o   ディレクトリ /lib と /usr/lib をこの順番で検索する。

そのライブラリが他の共有ライブラリに依存している場合は、 依存しているライブラリも動的リンカが同じ検索ルールに基づいて 自動的にロードする (それらのライブラリにさらに依存関係がある場合などは この処理は再帰的に行われる)。

flag には以下の 2 つの値のいずれかを含めなければならない:

RTLD_LAZY
    lazy binding (手抜きなシンボルの結び付け) が行う。 シンボルの解決はそのシンボルを参照するコードが実行されるときにのみ 行われる。シンボルが一度も参照されなかった場合には、そのシンボルは 解決されないままとなる。 (lazy binding は関数参照についてのみ実施される; 変数への参照は常に ライブラリがロードされた時点で直ちに解決される。)

RTLD_NOW
    この値が指定されるか、環境変数 LD_BIND_NOW に空でない文字列が設定された場合、 ライブラリ中の未定義のシンボルを全て解決してから dlopen() は復帰する。解決できなかったときにはエラーが返される。

以下の値のうち 0 個以上を論理和 (OR) の形で flag に追加することもできる:

RTLD_GLOBAL
    このライブラリで定義されているシンボルが、これより後でロードされる ライブラリのシンボル解決で利用できるようになる。

RTLD_LOCAL
    このフラグは RTLD_GLOBAL の反対の意味であり、どちらのフラグも指定されなかった場合は こちらがデフォルトとなる。 このライブラリで定義されているシンボルは、これより後でロードされる ライブラリでのシンボル参照で利用できない。

RTLD_NODELETE (glibc 2.2 以降)
    dlclose() 中にそのライブラリをアンロードしない。 そのため、同じライブラリをこれ以降に dlopen() で再度ロードした場合に、ライブラリ内の静的変数は再初期化されない。 このフラグは POSIX.1-2001 では規定されていない。

RTLD_NOLOAD (glibc 2.2 以降)
    そのライブラリをロードしない。 このフラグはそのライブラリがすでに組み込まれているかを検査するのに 利用できる (dlopen() は、ライブラリが組み込まれていなければ NULL を返し、 すでに組み込まれていればそのライブラリのハンドルを返す)。 また、すでにロードされているライブラリのフラグを昇格させるのにも 利用できる。例えば、過去に RTLD_LOCAL でロードしたライブラリを RTLD_NOLOAD | RTLD_GLOBAL で再オープンすることができる。 このフラグは POSIX.1-2001 では規定されていない。

RTLD_DEEPBIND (glibc 2.3.4 以降)
    このライブラリ内のシンボルの参照領域をグローバル領域よりも前に配置する。 つまり、内蔵型のライブラリでは、すでにロードされたライブラリに含まれる 同じ名前のグローバルなシンボルよりも自ライブラリ内のシンボルが優先して 使われる。 このフラグは POSIX.1-2001 では規定されていない。

filename が NULL ポインタである場合は、 返されるハンドルはメイン・プログラムのものになる。 このハンドルが dlsym() に渡されると、シンボルの検索は、メイン・プログラム内、 プログラムの起動時にロードされる全ての共有ライブラリ、 dlopen() によって RTLD_GLOBAL フラグ付きでロードされた全ての共有ライブラリ、の順序で行われる。

オープンされたライブラリ中での外部参照は、 そのライブラリの依存リストにあるライブラリか、 RTLD_GLOBAL フラグ付きで既にオープンされているライブラリを使って解決される。 実行ファイルが "-rdynamic" フラグ ("--export-dynamic" も同義) 付きでリンクされている場合は、実行ファイル中のグローバルシンボルも、 動的にロードされるライブラリ内の参照解決に用いられる。

同じライブラリが dlopen() によって再度ロードされた場合には、同じファイルハンドルが返される。 dl ライブラリはライブラリハンドルのリンク数を管理している。 したがって動的ライブラリは dlclose() が dlopen() と同じ回数だけ呼び出されない限りアンロードされない。 _init() ルーチンは一度だけ呼び出される (_init() が存在する場合のみ)。 RTLD_NOW が指定されて dlopen() が呼び出された場合、 RTLD_LAZY で以前にロードされたライブラリのシンボル解決が実行されることがある。

dlopen() は、何らかの理由で失敗すると NULL を返す。

  dlsym()
関数 dlsym() は、 dlopen() が返した動的ライブラリの「ハンドル」と、 NULL 終端されたシンボル名の文字列を引き数に取り、 そのシンボルがロードされたメモリのアドレスを返す。 シンボルが、指定されたライブラリと、指定されたライブラリがロードされる際に dlopen() が自動的にロードしてライブラリのいずれにも見つからない場合には、 dlsym() は NULL を返す (dlsym() による検索は、これらのライブラリの依存関係のツリーを先頭から 辿って行われる)。 実際にはシンボルの値自体が NULL になることもある (そのため、 dlsym() の返り値が NULL であったとしても必ずしもエラーという訳ではない)。 エラーかどうかを確認する正しい方法は以下の通りである: dlerror() を呼び出して以前のエラー状態をクリアしてから、 dlsym() を呼び出す。その後でもう一度 dlerror() を呼び出して、 dlerror() の返り値を変数に保存し、保存した値が NULL であるか判定する。

RTLD_DEFAULT と RTLD_NEXT という二つの特別な擬似ハンドルがある。 RTLD_DEFAULT は、デフォルトのライブラリ検索順序にしたがって、 検索対象のシンボルが最初に現れるところを探す。 RTLD_NEXT は、ライブラリ検索順序の中で現在のライブラリ以降で最初に 関数が現れるところを探す。この機能を使うことで、別の共有ライブラリの 関数へのラッパーを提供することができる。

  dlclose()
関数 dlclose() は動的ライブラリのハンドル handle の参照カウントを 1 減らす。参照カウントが 0 になり、ロードされている 他のライブラリからそのライブラリ内のシンボルが使われていなければ、 その動的ライブラリをアンロードする。

関数 dlclose() は、成功した場合は 0 を返し、エラーの場合 0 以外を返す。

  廃止されたシンボル _init() と _fini()
リンカは _init と _fini を特別なシンボルと解釈する。 ある動的ライブラリで _init() という名前のルーチンがエクスポートされていれば、 そのコードは、ライブラリのロード後、かつ dlopen() が復帰する前に実行される。 その動的ライブラリで _fini() という名前のルーチンがエクスポートされていれば、 ライブラリがアンロードされる直前にそのルーチンが呼び出される。 システムの起動ファイルに対するリンクを避ける必要がある場合、 gcc(1) のコマンドラインに -nostartfiles オプションを指定すればよい。

このルーチンや、gcc のオプション -nostartfiles や -nostdlib は使用しないことを推奨する。 これらを使うと、望ましくない動作をすることがある。 なぜなら、(特別な措置が行われない限り) これらの constructor/destructor ルーチンは実行されないからである。

代わりに、ライブラリは __attribute__((constructor)) や __attribute__((destructor)) の関数属性を使って必要なルーチンをエクスポートするのがよい。 これらについては gcc の info ページを参照のこと。 constructor ルーチンは dlopen() が復帰する前に実行され、 destructor ルーチンは dlclose() が復帰する前に実行される。

  GNU での拡張: dladdr() と dlvsym()
glibc では POSIX には記載されていない関数が 2つ追加されている。 プロトタイプは以下の通りである。

#define _GNU_SOURCE         /* feature_test_macros(7) 参照 */
#include <dlfcn.h>

int dladdr(void *addr, Dl_info *info);

void *dlvsym(void *handle, char *symbol, char *version);

関数 dladdr() は、関数のポインタを引き数にとり、関数の名前と関数が定義されている ファイルの解決を試みる。情報は Dl_info 構造体に格納される。

typedef struct {
    const char *dli_fname;  /* Pathname of shared object that
                               contains address */
    void       *dli_fbase;  /* Address at which shared object
                               is loaded */
    const char *dli_sname;  /* Name of nearest symbol with address
                               lower than addr */
    void       *dli_saddr;  /* Exact address of symbol named
                               in dli_sname */
} Dl_info;

addr にマッチするシンボルが見つからなかった場合、 dli_sname と dli_saddr は NULL にセットされる。

dladdr() は、エラー時には 0 を返し、成功した場合は 0 以外を返す。

関数 dlvsym() は dlsym() と同じ動作をするが、バージョンの文字列を渡す引き数が 追加されている点が異なる (dlvsym() はバージョン 2.1 以降の glibc で提供されている)。

準拠
POSIX.1-2003 には dlclose(), dlerror(), dlopen(), dlsym(). の記載がある。

注意
シンボル RTLD_DEFAULT と RTLD_NEXT は <dlfcn.h> で定義されており、 <dlfcn.h> のインクルード前に _GNU_SOURCE が定義されている場合のみ有効となる。 glibc 2.2.3 以降では、 atexit(3) を使って、ライブラリがアンロードされる際に自動的に呼び出される 終了ハンドラ (exit handler) を登録することができる。

  歴史
dlopen インターフェースの標準は SunOS をもとにしている。 SunOS には dladdr() もあったが、 dlvsym() はなかった。

バグ
時として、 dladdr() に渡した関数ポインタは驚くような値になることがある。 いくつかのアーキテクチャ (特に i386 と x86_64) では、 引き数として使用した関数が動的リンクライブラリで定義されるもので あったとしても、 dli_fname と dli_fbase が dladdr() を呼び出したオブジェクトを参照した状態で終わっていることがある。

問題は、関数ポインタの解決は今なおコンパイル時に行われるが、 そのポインタは元のオブジェクトの plt (Procedure Linkage Table) セクションを指しているだけだという点にある (オブジェクト自体は、ダイナミックリンカによってシンボルの解決が行われた後に、 関数の呼び出しを行う)。 これに対処する方法としては、 コードを position-independent でコンパイルするという方法がある。 そうすると、コンパイラはコンパイル時にポインタを用意することができず、 今日の gcc(1) では、実行時に dladdr() に関数ポインタを渡す前に、 got (Global Offset Table) から最終的なシンボルのアドレスをロードするだけの コードが生成される。

例
math ライブラリをロードし、2.0 の余弦を表示する
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int
main(int argc, char **argv)
{
    void *handle;
    double (*cosine)(double);
    char *error;
    handle = dlopen("libm.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    dlerror();    /* Clear any existing error */
    /* Writing: cosine = (double (*)(double)) dlsym(handle, "cos");
       would seem more natural, but the C99 standard leaves
       casting from "void *" to a function pointer undefined.
       The assignment used below is the POSIX.1-2003 (Technical
       Corrigendum 1) workaround; see the Rationale for the
       POSIX specification of dlsym(). */
    *(void **) (&cosine) = dlsym(handle, "cos");
    if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }
    printf("%f\n", (*cosine)(2.0));
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

このプログラムを "foo.c" に書いたとすると、以下のコマンドでプログラムを ビルドできる。

    gcc -rdynamic -o foo foo.c -ldl

_init() と _fini() をエクスポートするライブラリの場合は 以下のようにしてコンパイルする必要がある。 例として bar.c をコンパイルする場合:

    gcc -shared -nostartfiles -o bar bar.c

関連項目
ld(1), ldd(1), dl_iterate_phdr(3), rtld-audit(7), ld.so(8), ldconfig(8) ld.so info pages, gcc info pages, ld info pages

この文書について
この man ページは Linux man-pages プロジェクトのリリース 3.54 の一部 である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。