DLOPEN(3) | Linux Programmer's Manual | 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() が呼び出された後に、 dlopen(), dlsym(), dlclose() のいずれかで最後に発生したエラーについての説明メッセージを返す。 初期化後または前回呼び出された後で、エラーが発生していなければ NULL を返す。
関数 dlopen() は、ヌル終端された文字列 filename で指定されたファイル名の動的ライブラリ (dynamic library) をロードし、 その動的ライブラリへの内部「ハンドル」を返す。 filename が NULL の場合、メインプログラムへのハンドルが返される。 filename がスラッシュ ("/") を含む場合、(相対か絶対かの)パス名として解釈される。 それ以外の場合、動的リンカーは以下の手順でライブラリを検索する (詳細は ld.so(8) を参照):
そのライブラリが他の共有ライブラリに依存している場合は、 依存しているライブラリも動的リンカーが同じ検索ルールに基づいて 自動的にロードする (それらのライブラリにさらに依存関係がある場合などは この処理は再帰的に行われる)。
flag には以下の 2 つの値のいずれかを含めなければならない:
以下の値のうち 0 個以上を論理和 (OR) の形で flag に追加することもできる:
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() は、 dlopen() が返した動的ライブラリの「ハンドル」と、 NULL 終端されたシンボル名の文字列を引き数に取り、 そのシンボルがロードされたメモリーのアドレスを返す。 シンボルが、指定されたライブラリと、指定されたライブラリがロードされる際に dlopen() が自動的にロードしてライブラリのいずれにも見つからない場合には、 dlsym() は NULL を返す (dlsym() による検索は、これらのライブラリの依存関係のツリーを先頭から 辿って行われる)。 実際にはシンボルの値自体が NULL になることもある (そのため、 dlsym() の返り値が NULL であったとしても必ずしもエラーという訳ではない)。 エラーかどうかを確認する正しい方法は以下の通りである: dlerror() を呼び出して以前のエラー状態をクリアしてから、 dlsym() を呼び出す。その後でもう一度 dlerror() を呼び出して、 dlerror() の返り値を変数に保存し、保存した値が NULL であるか判定する。
RTLD_DEFAULT と RTLD_NEXT という二つの特別な擬似ハンドルがある。 RTLD_DEFAULT は、デフォルトのライブラリ検索順序にしたがって、 検索対象のシンボルが最初に現れるところを探す。 RTLD_NEXT は、ライブラリ検索順序の中で現在のライブラリ以降で最初に 関数が現れるところを探す。この機能を使うことで、別の共有ライブラリの 関数へのラッパーを提供することができる。
関数 dlclose() は動的ライブラリのハンドル handle の参照カウントを 1 減らす。参照カウントが 0 になり、ロードされている 他のライブラリからそのライブラリ内のシンボルが使われていなければ、 その動的ライブラリをアンロードする。
関数 dlclose() は、成功した場合は 0 を返し、エラーの場合 0 以外を返す。
リンカーは _init と _fini を特別なシンボルと解釈する。 ある動的ライブラリで _init() という名前のルーチンがエクスポートされていれば、 そのコードは、ライブラリのロード後、かつ dlopen() が復帰する前に実行される。 その動的ライブラリで _fini() という名前のルーチンがエクスポートされていれば、 ライブラリがアンロードされる直前にそのルーチンが呼び出される。 システムの起動ファイルに対するリンクを避ける必要がある場合、 gcc(1) のコマンドラインに -nostartfiles オプションを指定すればよい。
このルーチンや、gcc のオプション -nostartfiles や -nostdlib は使用しないことを推奨する。 これらを使うと、望ましくない動作をすることがある。 なぜなら、(特別な措置が行われない限り) これらの constructor/destructor ルーチンは実行されないからである。
代わりに、ライブラリは __attribute__((constructor)) や __attribute__((destructor)) の関数属性を使って必要なルーチンをエクスポートするのがよい。 これらについては gcc の info ページを参照のこと。 constructor ルーチンは dlopen() が復帰する前に実行され、 destructor ルーチンは dlclose() が復帰する前に実行される。
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/ に書かれている。
2014-10-02 | Linux |