DLOPEN(3)

名称
dlopen, dlsym, dlerror, dlclose - 動的リンカへのプログラムのインタフェース

ライブラリ
libc

書式
<dlfcn.h> void * dlopen(const char *path, int mode) void * dlsym(void *handle, const char *symbol) const char * dlerror(void) int dlclose(void *handle)

解説
これらの関数は、動的リンカサービスに対する単純なプログラムの インタフェースを提供します。 プログラムのアドレス空間に新しい共有オブジェクトを追加する操作、 共有オブジェクトが定義するシンボルのアドレス結合を取得する操作、 および共有オブジェクトを使用する必要がなくなったときに それを除去する操作が提供されています。

dlopen() は、 path 内の共有オブジェクトへのアクセスを提供し、 dlsym() と dlclose() の呼び出しにおいて、オブジェクトを後で参照するために使用可能な 記述子を返します。 dlopen() を呼び出す前に path がアドレス空間内になかった場合はアドレス空間に配置されます。 この方法で、オブジェクトが 初めてアドレス空間にロードされるとき、関数 _init() が存在すればそれが動的リンカによって呼び出されます。 先の dlopen() 呼び出しでアドレス空間内に path が既に配置されている場合、2 度目は追加されません。 しかし、 path 上での dlopen() 操作の参照カウント値は管理されます。 path に指定されたものが NULL ポインタであれば、プロセスの メイン実行可能モジュールへの参照として解釈されます。 mode は、ロードされたオブジェクトからの外部関数への参照が、 どのようにその参照先と結合するか、その方法を制御します。 これには次の値のうちの 1 つが含まれていなくてはなりません。 おそらくは、後で述べる追加フラグとの OR をとった値に なるでしょう。

RTLD_LAZY
    外部関数への参照はそれぞれ関数が最初に呼び出されるときに解決されます。

RTLD_NOW
    外部関数への参照はすべて dlopen() によってただちに結合されます。

RTLD_LAZY は、効率を良くするという理由で一般的に好ましいです。しかし、 dlopen() を呼び出している間に未定義シンボルを確実に発見するためには RTLD_NOW が便利です。

次のフラグの 1 つを OR をとって mode 引数の中に含めることができます。

RTLD_GLOBAL
    この共有オブジェクトから得られるシンボルおよび 必要とされるオブジェクトの有向非巡回グラフ (DAG) が、 その他すべての共有オブジェクトからの未解決の参照を 解決するために利用可能になります。

RTLD_LOCAL
    この共有オブジェクトから得られるシンボルおよび 必要とされるオブジェクトの有向非巡回グラフ (DAG) が、 同一の共有オブジェクトからの未解決の参照を 解決するために利用可能になります。 こちらがデフォルトの動作ですが、このフラグを用いて 明示的に指定することができます。

dlopen() は、処理が失敗すると NULL ポインタを返し、 dlerror() で問い合わせできるエラー状態を設定します。

dlsym() は、 symbol が handle で識別される共有オブジェクト内で 発生したときと同様に、ヌル文字で終了する文字列 symbol で記述されたシンボルのアドレス結合を返します。 dlopen() によってアドレス空間に追加されたオブジェクトによってエクスポートされる シンボルは、 dlsym() 呼び出しを通してのみアクセス可能です。 このようなシンボルは、オブジェクトがロードされる際に、すでに アドレス空間に存在しているシンボルの定義に置き換わるものでは ありませんし、通常の動的リンク参照を満たすために利用可能な ものでもありません。

dlsym() が特別な handle NULL を使用して呼び出された場合は、 そのハンドルは呼び出しがされている実行ファイルあるいは 共有オブジェクトへの参照として解釈されます。このため、 共有オブジェクトは自分自身の持つシンボルを 参照できるのです。

dlsym() が特別な handle RTLD_DEFAULT を使用して呼び出された場合は、オブジェクトが ロードされる際に未定義のシンボルを解決するために 使用されるアルゴリズムに従ってシンボルを探索します。 探索されるオブジェクトは次のように書かれた順番になります。

1.  参照しているオブジェクト自身 (あるいは dlsym() への呼び出しがされているオブジェクト)。ただし、オブジェクトが ld(1) に対して -Wsymbolic オプションを使用してリンクされた場合。

2.  プログラムの起動時にロードされていた全オブジェクト

3.  dlopen() 経由でロードされた全オブジェクト。これらのオブジェクトは、 参照を行っているオブジェクトも含んだ、必要とされるオブジェクトの 有向非巡回グラフに存在します。

4.  RTLD_GLOBAL フラグを mode 引数で設定して呼び出した dlopen() 経由でロードされた全オブジェクト

dlsym() が特別な handle RTLD_NEXT を使用して呼び出された場合、シンボルの検索は、 dlsym() 呼び出しを発行した後でロードされた共有オブジェクトに制限されます。 そのため、メインプログラムからこの関数が呼び出された場合、 すべての共有ライブラリが検索されます。この関数が共有ライブラリから 呼び出された場合、 その共有ライブラリより後でロードされる共有ライブラリすべてが 検索されます。 RTLD_NEXT はライブラリ関数を包み込むラッパを実装するのに便利です。 例えば、ラッパ関数 getpid() がある場合、関数内で dlsym(RTLD_NEXT, "getpid") を使用すれば “本物の” getpid() にアクセスできるでしょう。

シンボルが見つけられなかった場合、 dlsym() は null ポインタを返し、エラー状態を設定します。 エラー状態は dlerror() を使用して問い合わせることができます。

dlerror() は、 dlopen(), dlsym(), または dlclose() 呼び出しの間に発生した最後のエラーを記述する ヌル文字で終了する文字列を返します。 このようなエラーが発生していない場合、 dlerror() は NULL ポインタを返します。 dlerror() を呼び出すたびに、エラー指示がリセットされます。 そのため、後の呼び出しが先の呼び出しの直後にくるような dlerror() 呼び出しを行った場合、後の呼び出しでは必ず NULL ポインタが 返ってきます。

dlclose() は handle が参照する共有オブジェクトへの参照を削除します。 参照カウント値が減らされ 0 になると、そのオブジェクトはアドレス空間から 削除され handle は無効になります。この方法で共有オブジェクトを除去する直前に、 _fini() 関数がこのオブジェクトで定義されている場合には それを呼び出します。 dlclose() は、処理が成功すると値 0 を返します。 そうでない場合は -1 を返し、 エラー状態を設定します。エラー状態は dlerror() で問い合わせできます。

オブジェクトに固有の関数 _init() と _fini() は、引数なしに呼び出され、戻り値は期待されていません。

注
ELF 実行可能ファイルの場合、 ファイル中で定義されているシンボルを dlsym() で見えるようにするには ld(1) に対して -export-dynamic オプションを使用してリンクを行う必要があります。

以前の実装では、C 言語からコンパイルしたオブジェクトコードと シンボルの互換性を持たせることができるように、 外部シンボルにはすべてアンダスコアを先頭につける必要が ありました。現在でも、 C 言語のコンパイラに対して (非推奨ですが) -aout オプションを使用した場合には当てはまります。

エラー
dlopen() と dlsym() は、エラーが発生した場合には NULL ポインタを返します。 dlclose() は処理が成功すると 0 を返し、 エラーが発生した場合は -1 を返します。 エラーが検出されるたびに、エラーの詳細を説明するメッセージが dlerror() 呼び出しによって取り出せます。

関連項目
ld(1), rtld(1), dladdr(3), link(5)