Provided by: manpages-ja-dev_0.5.0.0.20210215+dfsg-1_all
名前
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() は、ヌル終端された文字列 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 symbol whose definition overlaps 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 */ cosine = (double (*)(double)) dlsym(handle, "cos"); /* ISO の C 標準によれば、上のような、関数ポインターと 'void *' 間の キャストを行った場合に得られる結果は不定である。 POSIX.1-2003 と POSIX.1-2008 では、この状況は認められており、 以下のようなワークアラウンドが提案されている。 *(void **) (&cosine) = dlsym(handle, "cos"); この (ぶかっこうな) キャストは ISO の C 標準に従っており、 コンパイラの警告を避けることができる。 POSIX.1-2008 の 2013 Technical Corrigendum (別名 POSIX.1-2013) では、 POSIX に準拠する実装では 'void *' から関数ポインターへの キャストをサポートすることが要求されるようになり、状況が改善 された。にもかかわらず、('-pedantic' オプションを指定した gcc などの) いくつかのコンパイラは、このプログラムで使用されている キャストについて文句を言うのだ。 error = dlerror(); if (error != 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), pldd(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.79 の一部 である。プロジェクト の説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。