「動的実行プログラム」を初期化および実行するとき、アプリケーションとその依存関係を結合させるために、インタプリタが呼び出されます。Solaris では、このインタプリタを実行時リンカーと呼びます。
動的実行プログラムのリンク編集中に、特別な .interp セクションが、関連するプログラムのヘッダーとともに作成されます。このセクションには、プログラムのインタプリタを指定するパス名が組み込まれています。リンカーによって提供されたデフォルトの名前は、 32 ビットの実行プログラムの場合は実行時リンカー /usr/lib/ld.so.1、 64 ビットの実行プログラムの場合は /usr/lib/64/ld.so.1 となります。
ld.so.1 は、共有オブジェクトの特殊なケースです。ここではバージョン番号 1 が使われています。しかし、Solaris の今後のリリースによってバージョンアップされる可能性があります。
動的オブジェクトの実行プロセス中に、カーネルはファイルを読み込んで、プログラムのヘッダー情報を読み取ります。詳細は、プログラムヘッダーを参照してください。この情報を使って、カーネルは必要なインタプリタの名前を検出します。カーネルは、読み込んだインタプリタに制御を転送し、同時に重要な情報を渡して、インタプリタがアプリケーションの結合を続行してからそのアプリケーションを実行できるようにします。
アプリケーションの初期化に加え、実行時リンカーは、アプリケーションが自分のアドレス空間を拡張できるようにするサービスも提供します。この処理には、追加のオブジェクトの読み込みとそのシンボルへの結合が含まれます。
実行時リンカーでは、以下の処理が行われます。
これらの依存関係内に配置および読み込みを行い、動的情報セクションを分析して、追加の依存関係が必要かどうか判定する
必要な再配置を実行し、これらのオブジェクトをプロセスの実行に備えて結合する
依存関係によって作成された初期設定関数を呼び出す
アプリケーションに制御を渡す
アプリケーションの実行中に、遅延された関数の結合を実行するよう要求される
アプリケーションも、実行時リンカーサービスに、dlopen(3DL) によって追加のオブジェクトを入手するよう要求し、dlsym(3DL) を使用してこれらのオブジェクト内のシンボルに結合する
実行時リンカーがプログラムのメモリーセグメントを作成するとき、依存性は、プログラムのサービスを提供するためにどの共有オブジェクトが必要であるかを示します。 参照された共有オブジェクトとそれが依存するものを繰り返し結合することによって、実行時リンカーは完全なプロセスイメージを生成します。
共有オブジェクトが依存性リストにおいて複数回参照されるときでも、実行時リンカーはこの共有オブジェクトをプロセスに 1 回だけ結合します。
動的実行プログラムのリンク編集中に、1 つまたは複数の共有オブジェクトが明示的に参照されます。これらのオブジェクトは、依存関係として動的実行プログラム内に記録されます。
実行時リンカーはこの依存情報を使用して、関連オブジェクトを検索して読み込みます。これらの依存関係は、実行プログラムのリンク編集中に参照された順番で処理されます。
動的実行プログラムの依存関係がすべて読み込まれると、これらの依存関係も読み込まれた順番に検査され、追加の依存関係が配置されます。この処理は、すべての依存関係の配置と読み込みが完了するまで続きます。この技術の結果、すべての依存関係が幅優先順になります。
実行時リンカーは、依存関係を探す際、デフォルトでは 1 つの場所を検索します。この場所は、32 ビットオブジェクトを処理する場合は /usr/lib、64 ビットオブジェクトを処理する場合は /usr/lib/64 です。単純なファイル名で指定された依存関係の前には、このデフォルトのディレクトリ名が付きます。このパス名を使用して、実際のファイルを見つけます。
動的実行プログラムまたは共有オブジェクトの依存関係は、ldd(1) を使用して表示できます。たとえば、ファイル /usr/bin/cat には次のような依存関係があります。
$ ldd /usr/bin/cat libc.so.1 => /usr/lib/libc.so.1 libdl.so.1 => /usr/lib/libdl.so.1 |
ファイル /usr/bin/cat は、ファイル libc.so.1 と libdl.so.1 に対して依存関係を持つ、つまりこれらのファイルを必要とします。
オブジェクトに記録されている依存関係は、dump(1) を使用して調べられます。このコマンドを使用すると、ファイルの .dynamic セクションを表示して、NEEDED タグがついているエントリを探すことができます。次の例では、上記の ldd(1) の例で表示された依存関係 libdl.so.1 は、ファイル /usr/bin/cat 内に記録されていません。ldd(1) は、指定されたファイルの「すべての」依存関係を示していて、libdl.so.1 は、実際に /usr/lib/libc.so.1 の依存関係にあります。
$ dump -Lvp /usr/bin/cat /usr/bin/cat: [INDEX] Tag Value [1] NEEDED libc.so.1 ......... |
上記の dump(1) の例では、依存関係は単純なファイル名として表示されています。つまり、ファイル名に「/」が含まれていません。実行時リンカーが一連の規則に従ってパス名を生成するためには、単純なファイル名を使用する必要があります。「/」が組み込まれたファイル名は、そのまま使用されます。
単純なファイル名の記録は、標準的な、依存関係を記録する最も柔軟性の高いメカニズムです。リンカーに -h オプションを指定すると、依存関係内の単純な名前が記録されます。命名規約 および 共有オブジェクト名の記録 を参照してください。
依存関係は、しばしば、/usr/lib あるいは /usr/lib/64 以外に分散されます。動的実行プログラムまたは共有オブジェクトが、他のディレクトリに依存関係を配置する必要がある場合、実行時リンカーは、明示的に、このディレクトリを検索するように指示されます。
追加の検索パスは、オブジェクトごとに、オブジェクトのリンク編集中に「実行パス」を記録して指定できます。この情報の記録方法の詳細については、実行時リンカーが検索するディレクトリ を参照してください。
記録された「実行パス」は、dump(1) を使用して表示できます。RUNPATH タグの付いた .dynamic エントリを例示します。次の例では、prog は libfoo.so.1 上に依存関係を持っています。実行時リンカーは、デフォルトの場所を調べる前に、ディレクトリ/home/me/lib と/home/you/lib を検索しなければなりません。
$ dump -Lvp prog prog: [INDEX] Tag Value [1] NEEDED libfoo.so.1 [2] NEEDED libc.so.1 [3] RUNPATH /home/me/lib:/home/you/lib ......... |
実行時リンカーの検索パスに追加するもう 1 つの方法は、環境変数 LD_LIBRARY_PATH
を設定する方法です。この環境変数は、プロセスの始動時に 1 度分析され、コロンで区切られたディレクトリのリストに設定できます。実行時リンカーは、このリストに設定したディレクトリを、指定された「実行パス」またはデフォルトのディレクトリよりも前に検索します。
これらの環境変数は、アプリケーションを強制的にローカルな依存関係に結合するといったデバッグの目的に適しています。次の例では、上記の例のファイル prog は、現在の作業ディレクトリ内で検出された libfoo.so.1 に結合されます。
$ LD_LIBRARY_PATH=. prog |
環境変数 LD_LIBRARY_PATH
の使用は、実行時リンカーの検索パスに影響する一時的なメカニズムとしては有用ですが、製品版ソフトウェアの場合は大きな支障があります。この環境変数を参照できる動的実行プログラムは、その検索パスを拡張させます。これにより、全体のパフォーマンスが低下する場合があります。また、環境変数の使用と実行時リンカーが検索するディレクトリで説明しているとおり、LD_LIBRARY_PATH
はリンカーに影響を及ぼします。
環境変数で検索パスを指定した場合、64 ビットの実行プログラムが、目的の名前と一致する 32 ビットのライブラリが格納されているパスを検索することもあり得ます。あるいは、その逆の場合もあります。このような場合、実行時リンカーは有効でない
32 ビットのライブラリを拒否し、次の検索パスを検索して、目的の名前と一致する有効な 64 ビットのライブラリを探します。一致するものが見つからない場合には、エラーメッセージが表示されます。このエラーを詳細に監視するには、LD_DEBUG
環境変数を設定して、ファイルのトークンを取り込みます。デバッギングライブラリを参照してください。
$ LD_LIBRARY_PATH=/usr/bin/64 LD_DEBUG=files /usr/bin/ls ... 00283: file=libc.so.1; needed by /usr/bin/ls 00283: 00283: file=/usr/lib/64/libc.so.1 rejected: ELF class mismatch: \ 00283: 32-bit/64-bit 00283: 00283: file=/usr/lib/libc.so.1 [ ELF ]; generating link map 00283: dynamic: 0xef631180 base: 0xef580000 size: 0xb8000 00283: entry: 0xef5a1240 phdr: 0xef580034 phnum: 3 00283: lmid: 0x0 00283: 00283: file=/usr/lib/libc.so.1; analyzing [ RTLD_GLOBAL RTLD_LAZY ] ... |
依存関係が配置できない場合は、ldd(1) により、オブジェクトが検出できないことが表示されます。アプリケーションを実行しようとすると、実行時リンカーから該当するエラーメッセージが表示されます。
$ ldd prog libfoo.so.1 => (file not found) libc.so.1 => /usr/lib/libc.so.1 libdl.so.1 => /usr/lib/libdl.so.1 $ prog ld.so.1: prog: fatal: libfoo.so.1: open failed: No such file or directory |
実行時リンカーが使用するデフォルトの検索パスは /usr/lib または /usr/lib/64 です。 このような検索パスを管理するには、 crle(1) ユーティリティで作成する実行時構成ファイルを使用します。 このファイルは、正しい「実行パス」で作成されなかったアプリケーションについて検索パスを設定する場合に便利です。
構成ファイルが作成されるデフォルトの場所は、32 ビットアプリケーションの場合は /var/ld/ld.config、64 ビットアプリケーションの場合は /var/ld/64/ld.config です。このファイルは、システム上の、それぞれのタイプのアプリケーションすべてに影響します。構成ファイルはこれ以外の場所にも作成でき、実行時リンカーの LD_CONFIG 環境変数を使用してこれらのファイルを選択できます。後者の方法は、構成ファイルをデフォルトの場所にインストールする前にテストする場合に便利です。
実行時リンカーは、さまざまな動的ストリングトークンを展開できます。このようなトークンは、フィルタ、「実行パス」、および依存関係の定義に利用できます。
$ISALIST – 当該プラットフォームで実行できるネイティブな命令セットに展開する。命令セット固有の共有オブジェクトを参照。
$ORIGIN – オブジェクトの読み込み元ディレクトリを指定する。関連する依存関係の配置を参照してください。
$OSNAME – オペレーティングシステムの名前に展開する。システム固有の共有オブジェクトを参照
$OSREL – オペレーティングシステムのリリースレベルに展開する。システム固有の共有オブジェクトを参照
$PLATFORM – 当該マシンのプロセッサタイプに展開する。システム固有の共有オブジェクトを参照
アプリケーションが要求する依存関係をすべて読み込んだ後、実行時リンカーは各オブジェクトを処理し、必要な再配置すべてを実行します。
オブジェクトのリンク編集中に、入力再配置の可能なオブジェクトとともに提供された再配置の情報が、出力ファイルに適用されます。ただし、動的実行プログラムまたは共有オブジェクトを作成している場合には、ほとんどの再配置は、リンク編集時には完了できません。それは、再配置には、オブジェクトがメモリー内に読み込まれる時だけ入手することができる、論理アドレスが必要だからです。このような場合、リンカーは新しい再配置を出力ファイルイメージの一部として記録します。実行時リンカーは、新しい再配置レコードを処理する必要があります。
再配置のさまざまなタイプの詳細については、再配置型 (プロセッサ固有) を参照してください。再配置には、大きく分けて 2 つのタイプがあります。
オブジェクトの再配置記録は、dump(1) を使用して表示できます。次の例では、ファイル libbar.so.1 には、「大域オフセットテーブル」(.got セクション) が更新される必要があることを示す、2 つの再配置記録が組み込まれています。
$ dump -rvp libbar.so.1 libbar.so.1: .rela.got: Offset Symndx Type Addend 0x10438 0 R_SPARC_RELATIVE 0 0x1043c foo R_SPARC_GLOB_DAT 0 |
最初の再配置は、単純な相対再配置です。このことは、再配置タイプとシンボルインデックス (Symndx) フィールドがゼロであることから分かります。この再配置では、オブジェクトがメモリーに読み込まれたベースアドレスを使用して、関連する .got オフセットを更新する必要があります。
2 番目の再配置では、シンボル foo のアドレスが必要です。この再配置を完了させるには、実行時リンカーが、これまでに読み込まれた動的実行プログラムと依存関係からこのシンボルを配置する必要があります。
オブジェクトがシンボルを必要とする場合、実行時リンカーはそのシンボルを、オブジェクトのシンボル要求の検索範囲と、プロセス内の各オブジェクトによって提供されるシンボルの可視性に基づいて検索します。これらの属性は、読み込まれる時に、オブジェクトのデフォルトとして使用され、dlopen(3DL) の特別なモードとしても使用され、さらに、場合によっては、オブジェクトの構築時に、オブジェクト内に記録されます。
平均的なユーザーであれば、動的実行プログラムとその依存関係、および dlopen(3DL) を通じて入手したオブジェクトに適用されるデフォルトのシンボル検索モードが、理解できるようになります。前者の検索モードについては、次の項、デフォルトの検索で概要を説明します。種々のシンボル検索に活用できる後者については、シンボル検索で説明しています。
動的なオブジェクトで直接結合を行うと、別のシンボル検索モデルが提供されます。 このモデルでは、実行時リンカーは、リンク編集時に結合されたオブジェクトからシンボルを直接検索します。直接結合を参照してください。
動的実行プログラムと、ともに読み込まれるすべての依存関係には、「ワールド」検索範囲と、「大域」シンボル可視性が割り当てられます 。シンボル検索を参照してください。 実行時リンカーは、動的実行プログラムまたはこの実行プログラムとともに読み込まれた依存関係すべてを調べてシンボルを検出するために、オブジェクトを順番に検索します。まず動的実行プログラムから検索してから、 オブジェクトが読み込まれた順番に依存関係を検索します。
前の項で説明したように、ldd(1) を使用すると、動的実行プログラムの依存関係は読み込まれた順番にリストされます。たとえば、共有オブジェクト libbar.so.1 がシンボル foo の再配置を行うためにそのアドレスを必要とし、かつ共有オブジェクトが動的実行プログラム prog の依存関係であるとします。
$ ldd prog libfoo.so.1 => /home/me/lib/libfoo.so.1 libbar.so.1 => /home/me/lib/libbar.so.1 |
実行時リンカーは、foo の検索を、最初に動的実行プログラム prog 内で実行し、次に共有オブジェクト /home/me/lib/libfoo.so.1 内で、最後に共有オブジェクト /home/me/lib/libbar.so.1 内で実行します。
シンボル検索は、シンボル名のサイズが増大し依存関係の数が増加すると、特にコストのかかる処理になる可能性があります。このパフォーマンスについての詳細は、性能に関する考慮事項で説明しています。これに代わる検索モデルについては、直接結合を参照してください。
最初に動的実行プログラム内でシンボルの検索を行い、次に各依存関係内で検索を行うという実行時リンカーのデフォルトのメカニズムは、要求されたシンボルの最初の出現が、この検索を満足させることを意味しています。そのため、同じシンボルの複数のインスタンスが存在する場合は、最初のインスタンスが、ほかのすべてのインスタンスに割り込みされます。共有オブジェクトの処理も参照してください。
直接結合を行うオブジェクトを作成すると、参照されるシンボルと、定義を提供する依存との関係は、作成されるオブジェクトに記録されます。実行時リンカーは、デフォルトのシンボル検索モデルを使用する代わりに、この情報を使って関連するオブジェクトから直接シンボルを検索します。直接結合情報は、リンク編集で指定される依存関係に対してのみ確立されます。したがって、-z defs オプションを推奨します。
直接結合は、以下のメカニズムのどれかを使用して確立できます。
-B direct オプションを使用する。このオプションは、構築されるオブジェクトとそのすべての依存関係との間に直接結合を確立する。-B direct オプションを使用すると、遅延読み込みも有効になる。これは、リンク編集のコマンド行の先頭に -z lazyload オプションを指定するのと同じことである。動的依存関係の遅延読み込みを参照
-z direct オプションを使用する。このオプションは、構築されるオブジェクトと、コマンド行上でこのオプションに続いて指定される依存関係との間に直接結合を確立する。このオプションは、-z nodirect オプションと併用して、依存関係との間の直接結合を使用するかどうかを切り替えることができる
DIRECT mapfile 属性を使用する。この属性は、直接結合する個々のシンボルに対応する。個々のシンボルへの直接結合を防止するには、代わりに属性 NODIRECT を使用する。追加シンボルの定義を参照
直接結合モデルでは、多数のシンボル再配置や依存関係を伴う動的プロセスでのシンボル検索オーバーヘッドを大幅に削減できます。さらに、このモデルでは、同じ名前の複数のシンボルを、それらが直接結合されている個々のオブジェクトから見つけることができます。
直接結合ではデフォルトの検索モデルがバイパスされるため、従来から使用されている割り込みシンボルが迂回される可能性があります。デフォルトのモデルでは必ず、1 つのシンボルへのすべての参照は同じ 1 つの定義に結合されます。
直接結合環境でも、オブジェクト単位で、割り込みを行うことができます。それには、オブジェクトが割り込み処理として識別される必要があります。環境変数 LD_PRELOAD
を使ってオブジェクトを読み込むか、リンカーの -z interpose オプションを使ってオブジェクトを作成すると、オブジェクトは割り込み処理として識別されます。 実行時リンカーは、直接結合されたシンボルを検索する際に、割り込み処理として識別されたオブジェクトを最初に探してから、シンボル定義を指定するオブジェクトを探します。
直接結合を実行時に無効にするには、環境変数 LD_NODIRECT
にヌル以外の値を設定します。
デフォルト手法の代替実装を提供するインタフェースがいくつか存在します。これらの実装は、自身が、プロセス内におけるその手法の唯一のインスタンスであることを前提とします。この例の 1 つにmalloc(3C) ファミリがあります。同じプロセスによってその手法のインスタンスが複数参照される可能性があるため、このようなファミリ内でのインタフェースへの直接結合は避ける必要があります。たとえば、プロセス内の 1 つの依存関係が libc.so.1 に直接結合し、一方で別の依存関係が libmapmalloc.so.1 に直接結合する可能性があります。
プロセスに単一の実装を提供するオブジェクトは、mapfile 指令の NODIRECT を使用してその実装にインタフェースを定義する必要があります。この指令によって、どのユーザーも実装に直接結合することなく、デフォルトのシンボル検索モデルを使用するよう、保証されます。
NODIRECT mapfile 指令は、コマンド行オプション -B direct や -z direct と組み合わせることができます。シンボルに NODIRECT を明示的に定義しないと、そのシンボルはコマンド行の指令に従います。
再配置は、実行されるタイミングで区別できます。この区別は、再配置されたオフセットに対する参照の種類によるもので、次のいずれかになります。
「即時参照」とは、オブジェクトが読み込まれたときにただちに決定しなければならない再配置のことです。この参照は、一般にオブジェクトコードで使用されるデータ項目、関数ポインタ、および位置依存共有オブジェクトからの関数呼び出しに対するものです。即時参照では、再配置された項目が参照されたことを実行時リンカーは認識できません。このため、すべての即時参照は、オブジェクトが読み込まれたら、アプリケーションが制御を獲得または再獲得する前に、再配置が完了する必要があります。
「遅延参照」とは、オブジェクトの実行時に決定できる再配置のことです。通常は、位置独立共有オブジェクトから大域関数への呼び出しか、動的実行可能ファイルから外部関数への呼び出しです。遅延参照を行う動的モジュールをコンパイルおよびリンク編集しているときに、関連付けられた関数呼び出しは、プロシージャリンクテーブルのエントリへの呼び出しに変換されます。 これらのエントリは、.plt セクションを構成します。 プロシージャのリンクテーブルの各エントリは、再配置への遅延参照になります。
プロシージャのリンクテーブルのエントリは、最初に呼び出されたときに、制御が実行時リンカーに渡るように生成されます。実行時リンカーは、関連付けられたオブジェクト内で必要なシンボルを検索し、情報を書き換え、その後のプロシージャリンクテーブルのエントリへの呼び出しが、関数を直接呼び出すようにします。遅延参照では、関数が最初に呼び出されるまで、再配置を遅延させることができます。この処理は、「遅延」結合と呼ばれることがあります。
実行時リンカーのデフォルトモードでは、プロシージャのリンクテーブルの再配置は、常に遅延結合として実行されます。デフォルトモードを無効にするには、環境変数 LD_BIND_NOW
にヌル以外の任意の値を設定します。この環境変数に値を設定すると、実行時リンカーは、オブジェクトが読み込まれてから、アプリケーションが制御を獲得または再獲得するまでの間に、即時参照および遅延参照を再配置します。たとえば、環境変数を次のように設定すると、ファイル prog とその依存先の再配置は、制御がアプリケーションに移る前に行われます。
$ LD_BIND_NOW=1 prog |
オブジェクトにアクセスするときは、RTLD_NOW として定義されたモードで、 dlopen(3DL) を使用することもできます。リンカーの -z now オプションを使用してオブジェクトを構築すれば、オブジェクトが読み込まれたときに再配置を完了させることができます。この再配置オプションは、実行時に指定したオブジェクトの依存先すべてに波及します。
前述の即時参照と遅延参照の例は、標準的なものですが、プロシージャのリンクテーブルエントリの作成は、リンク編集の入力として使用する再配置可能オブジェクトファイルが提供する再配置情報によって制御されます。R_SPARC_WPLT30 や R_386_PLT32 などの再配置レコードには、プロシージャのリンクテーブルエントリの作成が指定されています。これらのレコードは、位置独立コードで常に使用されます。ただし、動的実行可能ファイルの位置は固定されているため、リンク編集時に決定された外部関数参照は、元の再配置レコードに関係なく、プロシージャのリンクテーブルのエントリに変換できます。
最も一般的な再配置エラーは、シンボルが検出されるときに発生します。この状態になると、適切な実行時リンカーのエラーメッセージが表示され、アプリケーションは終了します。次に例を示します。
$ ldd prog libfoo.so.1 => ./libfoo.so.1 libc.so.1 => /usr/lib/libc.so.1 libbar.so.1 => ./libbar.so.1 libdl.so.1 => /usr/lib/libdl.so.1 $ prog ld.so.1: prog: fatal: relocation error: file ./libfoo.so.1: \ symbol bar: referenced symbol not found |
ファイル libfoo.so.1 内で参照されたシンボル bar は配置できません。
動的実行プログラムのリンク編集中に、このソートの再配置エラーが起こる可能性は、定義されていない重大なシンボルとしてフラグが付けられます。例について、実行可能ファイルの作成 を参照してください。この実行時の再配置エラーは、main のリンク編集が、bar のシンボル定義が組み込まれた共有オブジェクト libbar.so.1 の異なったバージョンを使用した場合、または -z nodefs オプションがリンク編集の一部として使用された場合に発生します。
即時参照として使用されたシンボルが配置できないために、このタイプの再配置エラーが発生した場合は、そのエラー状態は、プロセスの初期設定の直後にも発生します。遅延結合のデフォルトモードが原因で、遅延参照として使用されるシンボルが検出できない場合は、このエラー状態は、アプリケーションが制御を受け取ってから発生します。後者の場合、コードを実行する実行パスによって、エラー状態が発生するまでに数分または数ヶ月かかる場合もあり、あるいは発生しない場合もあります。
この種のエラーを防ぐためには、動的実行プログラムまたは共有オブジェクトの再配置の必要条件を、ldd(1) を使用して有効にしておきます。
ldd(1) とともに -d オプションを指定すると、すべての依存関係が出力され、すべての即時参照の再配置処理が実行されます。即時参照が解決できない場合には、診断メッセージが作成されます。上記の例でこのオプションを使用すると、次のようになります。
$ ldd -d prog libfoo.so.1 => ./libfoo.so.1 libc.so.1 => /usr/lib/libc.so.1 libbar.so.1 => ./libbar.so.1 libdl.so.1 => /usr/lib/libdl.so.1 symbol not found: bar (./libfoo.so.1) |
ldd(1) とともに -r オプションを指定すると、すべてのデータと遅延参照の再配置が処理されます。また、このどちらかの再配置が解決できない場合には、診断メッセージが作成されます。
また、実行時リンカーでは、プロセスの初期化中に新しいオブジェクトを取り込めるという、一歩進んだ柔軟性も提供しています。
環境変数 LD_PRELOAD
は、共有オブジェクトまたは再配置可能なオブジェクトのフレーム名、あるいは複数のフレーム名を空白で区切ったストリングに初期設定できます。これらのオブジェクトは、動的実行プログラムの後で、依存関係よりも前に読み込まれます。これらのオブジェクトには、「ワールド」検索範囲と「大域」シンボル可視性が割り当てられます。
$ LD_PRELOAD=./newstuff.so.1 prog |
動的実行プログラム prog が読み込まれ、次に共有オブジェクト newstuff.so.1 が続き、その次に prog 内に定義された依存関係が続きます。
これらのオブジェクトが処理される順番は、ldd(1) を使用して表示できます。
$ LD_PRELOAD=./newstuff.so.1 ldd prog ./newstuff.so.1 => ./newstuff.so libc.so.1 => /usr/lib/libc.so.1 |
次の例では、事前読み込みは少し複雑で時間がかかります。
$ LD_PRELOAD="./foo.o ./bar.o" prog |
実行時リンカーは、最初に再配置可能オブジェクト foo.o と bar.o をリンク編集し、メモリー内に保持されていた共有オブジェクトを生成します。次にこのメモリーイメージは、この前の例で示した共有オブジェクト newstuff.so.1 の事前読み込みと同じ方法で、動的実行プログラムとその依存関係との間に挿入されます。ここでも、これらのオブジェクトが処理される順番は、ldd(1) を使用して表示できます。
$ LD_PRELOAD="./foo.o ./bar.o" ldd prog ./foo.o => ./foo.o ./bar.o => ./bar.o libc.so.1 => /usr/lib/libc.so.1 |
オブジェクトを動的実行プログラムの後に挿入するこれらの機構は、別のレベルへ割り込むという発想に基づいています。これらの機構を使用すると、標準的な共有オブジェクト内に存在する関数の、新しい実装を試すことができます。この関数が組み込まれたオブジェクトをあらかじめ読み込むことにより、このオブジェクトは元のオブジェクトに割り込みます。そして、古い関数は、事前読み込みされた新しい関数によって完全に隠されてしまいます。
この他にも事前読み込みは、標準的な共有オブジェクト内に常駐する関数を補強するために使用できます。新しいシンボルが元のシンボルに割り込むことで、元の関数への呼び出し機能を保持しながら、新しい関数はいくつかの追加処理を実行できます。この機構には、元の関数に関連したシンボルエイリアスか、または元のシンボルのアドレスを検索する機能が必要です。
メモリーに動的オブジェクトが読み込まれる際、その動的オブジェクトに追加の依存関係がないか検査されます。デフォルトでは、依存関係があるとそれらがただちに読み込まれます。このサイクルは、依存関係のツリー全体を使い果たすまで続けられます。この時点で、再配置によって指定された内部オブジェクトの参照が、すべて解決します。
このデフォルトモデルでは、アプリケーションの依存関係がすべてメモリー内に読み込まれ、すべてのデータの再配置が実行されます。この処理は、これらの依存関係内のコードが実行中にアプリケーションによって実際に参照されるかどうかに関係なく、行われます。
遅延読み込みモデルでは、遅延読み込みのラベルが付いた依存関係は、明示的に参照が行われるまで読み込まれません。関数呼び出し遅延結合を利用して、依存関係の読み込みを最初に参照されるまで延期することができます。実際に参照されないオブジェクトは読み込まれません。
再配置参照は、即時か遅延です。即時参照はオブジェクトが初期化された時に解決される必要があるため、この参照を満たすすべての依存関係はすぐに読み込まれる必要があります。そのため、そういった依存関係を遅延読み込み可能として示すことは、あまり効果がありません。再配置が実行されるとき を参照してください。 動的オブジェクト間の即時参照は、概してあまり推奨されません。
遅延読み込みは、liblddbg というデバッギングライブラリを参照するリンカー自体によっても使用されます。デバッギングを呼び出すことはまれなので、リンカーを呼び出すたびにこのライブラリを読み込むことは不要で、コストがかさみます。このライブラリを遅延読み込みできるように指定することにより、その処理コストをデバッギング出力を必要とする読み込みに使うことができます。
遅延読み込みモデルを実行するための代替メソッドは、必要に応じて依存関係に dlopen() または dlsym() を実行することです。これは、dlsym() 参照の数が少ない場合、あるいはリンク編集時に依存関係の名前あるいはロケーションがわからない場合に最適です。名前やロケーションがわかっている依存関係のより複雑な相互作用については、通常のシンボル参照のコードを使用し、依存関係を遅延読み込みに指定する方が簡単です。
遅延読み込みあるいは通常の読み込みが実行されるようにオブジェクトに指定するには、それぞれリンカーオプション-z lazyload と-z nolazyload を使用します。これらのオプションは、リンク編集コマンド行の位置によって決まります。オプションに指定した依存関係には、オプションに指定されている読み込み属性が適用されます。デフォルトでは、-z nolazyload オプションが有効です。
次の単純なプログラムでは、libdebug.so.1 に対する依存関係が指定されています。 動的セクション (.dynamic) では、libdebug.so.1 に対して遅延読み込みが指定されています。シンボル情報セクション(.SUNW_syminfo)では、libdebug.so.1 の読み込みをトリガーするシンボル参照が指定されています。
$ cc -o prog prog.c -L. -zlazyload -ldebug -znolazyload -R'$ORIGIN' $ elfdump -d prog Dynamic Section: .dynamic index tag value [0] POSFLAG_1 0x1 [ LAZY ] [1] NEEDED 0x123 libdebug.so.1 [2] NEEDED 0x131 libc.so.1 [3] RUNPATH 0x13b $ORIGIN ... $ elfdump -y prog Syminfo section: .SUNW_syminfo index flgs boundto symbol ... [52] DL [1] libdebug.so.1 debug |
値に LAZY が指定された POSFLAG_1 は、次の NEEDED エントリ libdebug.so.1 が遅延読み込みされることを示しています。libc.so.1 は前に LAZY フラグがないので、プログラムの初期始動時に読み込まれます。
遅延読み込みを使用するには、アプリケーションで使用されるオブジェクト全体に渡り依存関係と「実行パス」を正確に宣言しなければならない場合があります。たとえば、libX.so 内のシンボルを参照する 2 つのオブジェクト libA.so と libB.so があるとします。libA.so は libX.so に対する依存性を宣言していますが、libB.so はこの宣言をしていません。通常、libA.so と libB.so が併用される場合、libB.so は libX.so を参照できます。これは、libA.so によってこの利用が可能になっているためです。しかし、libX.so が遅延読み込みされるように libA.so で宣言した場合には、libB.so が参照する時には libX.so を読み込めない可能性があります。libB.so で libX.so を依存関係として宣言していても、その位置固定に必要な「実行パス」を指定しなかった場合には、同様のエラーが発生する可能性があります。
遅延読み込みをするかしないかにかかわらず、動的オブジェクトではすべての依存関係とその位置固定方法を宣言することをお勧めします。遅延読み込みでは、この依存情報がより重要な意味合いを持ちます。
実行時に遅延読み込みを無効にするには、環境変数 LD_NOLAZYLOAD
をヌル以外の値に設定します。
制御をアプリケーションに引き渡す前に、実行時リンカーは、アプリケーションおよび読み込まれた依存関係内で検出された初期設定セクションを処理します。初期設定セクションである .preinit_array、.init_array、および .init は、動的オブジェクトの構築時にリンカーによって作成されます。
実行時リンカーは、.preinit_array セクションと .init_array セクションにアドレスが指定されている関数を実行します。これらの関数は、配列内でアドレスが出現する順序で実行されます。実行時リンカーは、.init セクションを独立した関数として実行します。1 つのオブジェクトに .init セクションと .init_array セクションの両方が含まれている場合は、そのオブジェクトに関しては、.init_array セクションによって定義されている関数よりも前に .init セクションが処理されます。
動的実行可能ファイルは、.preinit_array セクション内で「初期設定前」関数を提供することができます。これらの関数は、実行時リンカーがプロセスイメージを構築して再配置を実行し終わった後で、かつ他の初期設定関数の前に実行されます。「初期設定前」関数は、共有オブジェクト内では許可されません。
動的実行可能ファイル内のすべての .init セクションは、コンパイラドライバから供給されるプロセスの起動メカニズムによって、アプリケーション自体から呼び出されます。動的実行可能ファイルの .init セクションは、そのすべての依存関係の初期設定セクションが実行されたあとで、最後に呼び出されます。
動的オブジェクトは、終了セクションも提供できます。終了セクションである .fini_array および .fini は、動的オブジェクトが構築される際にリンカーによって作成されます。
終了セクションは、atexit(3C) によって記録できるように構成されます。これらのルーチンは、プロセスが exit(2) を呼び出したとき、またはオブジェクトが、dlclose(3DL) が指定された実行プロセスから除去されたときに呼び出されます。
実行時リンカーは、.fini_array セクションにアドレスが指定されている関数を実行します。これらの関数は、配列内でアドレスが出現する順序とは逆に実行されます。実行時リンカーは、.fini セクションを独立した関数として実行します。オブジェクトに .fini セクションと .fini_array セクションの両方が含まれている場合は、そのオブジェクトに関しては、.fini セクションによって定義されている関数よりも前に .fini_array セクションが処理されます。
動的実行プログラム内の .fini セクションは、コンパイラドライバから提供されるプロセスの終了メカニズムによってアプリケーション自体から呼び出されます。動的実行プログラムの .fini セクションは、そのすべての依存関係の終了セクションが実行される前に、最初に呼び出されます。
リンカーによる初期設定セクションと終了セクションの作成についての詳細は、初期設定および終了セクションを参照してください。
実行時にプロセス内で初期設定および終了コードをどのような順序で実行すべきかを判断することは、依存関係の分析を伴う複雑な問題を含んでいます。この処理は、初期設定セクションと終了セクションの導入以来、大きく発展してきました。この処理は、現代的な言語と現在のプログラミング手法の期待を実現しようとするものです。しかし、ユーザーの期待にこたえるのが難しい状況もあります。これらの状況を理解し、初期設定および終了コードの内容を制限することで、柔軟で予測可能な実行時動作が得られます。
Solaris 2.6 より前のリリースでは、依存関係の初期設定ルーチンが呼び出される順序は、読み込まれた順序の逆、つまり、ldd(1) を使用して表示される依存関係の順序とは逆でした。同様に、依存関係の終了ルーチンが呼び出される順序は、読み込まれた順序と同じでした。しかし、依存関係の階層が複雑化するにつれ、この単純な順序付け手法は適切とは言えなくなりました。
Solaris 2.6 リリースからは、実行時リンカーは、読み込まれたオブジェクトを位相的にソートしてリストを作成するようになりました。このリストは、各オブジェクトが表す依存関係の相関関係に加えて、示された依存関係の外部で発生したシンボル結合から構成されます。
初期設定セクションは、依存関係の位相的な順序とは逆に実行されます。循環性のある依存関係が検出された場合、循環の原因であるオブジェクトは、位相的にソートされません。循環性のある依存関係の初期設定セクションは、読み込まれた順序の逆に実行されます。同様に、終了ルーチンは、依存関係の位相的な順序で呼び出され、循環性のある依存関係は読み込まれた順序で実行されます。
-i オプションを指定した ldd(1) を使用すると、オブジェクトの依存関係の初期設定の順番を表示できます。たとえば、次の動的実行プログラムとその依存関係は、循環性のある依存関係を示しています。
$ dump -Lv B.so.1 | grep NEEDED [1] NEEDED C.so.1 $ dump -Lv C.so.1 | grep NEEDED [1] NEEDED B.so.1 $ dump -Lv main | grep NEEDED [1] NEEDED A.so.1 [2] NEEDED B.so.1 [3] NEEDED libc.so.1 $ ldd -i main A.so.1 => ./A.so.1 B.so.1 => ./B.so.1 libc.so.1 => /usr/lib/libc.so.1 C.so.1 => ./C.so.1 libdl.so.1 => /usr/lib/libdl.so.1 cyclic dependencies detected, group[1]: ./libC.so.1 ./libB.so.1 init object=/usr/lib/libc.so.1 init object=./A.so.1 init object=./C.so.1 - cyclic group [1], referenced by: ./B.so.1 init object=./B.so.1 - cyclic group [1], referenced by: ./C.so.1 |
Solaris 8 10/00 より前のリリースでは、環境変数 LD_BREADTH
にヌル以外の値を設定すると、リンカーで初期設定セクションと終了セクションを「Solaris
2.6 より前」の順序で実行することができました。多数のアプリケーションを初期化すると、依存関係が複雑になり、位相的な並び替えが必要になるため、この機能は
Solaris 8 10/00 から無効にされています。LD_BREADTH
の設定は無視され、メッセージは表示されません。
初期設定処理は、dlopen(3DL) が指定された実行中のプロセスに追加されたオブジェクトごとに繰り返されます。また、dlclose(3DL) に対する呼び出しの結果としてプロセスから読み込み解除されるすべてのオブジェクトに対して、終了処理も行われます。
依存関係を正確に示さない共有オブジェクトが多数存在するため、シンボルの結合は依存関係分析の一環として組み込まれます。シンボル結合を組み込むことでより正確な依存関係を生成できます。しかし、依存関係をすべて示していないオブジェクトにシンボル結合情報を加えても、オブジェクト全体の依存関係を決定するには不十分な場合があります。オブジェクトの読み込みに使用されるもっとも一般的なモデルは遅延結合です。このモデルの場合、初期設定処理の前に処理されるのは即時参照シンボル結合だけです。遅延参照からのシンボル結合が保留されていて、それまでに確立された依存関係をあとで拡張する可能性もあります。
オブジェクトの依存関係分析は不完全な場合があり、また循環性のある依存関係もしばしば存在するため、実行時リンカーは動的な初期設定も提供しています。この初期設定は、初期設定セクションを、同じオブジェクト内の関数が呼び出される前に実行しようとします。実行時リンカーは、遅延シンボル結合の際に、結合する先のオブジェクトの初期設定セクションがすでに呼び出されているかどうかを判定します。呼び出されていなければ、シンボル結合手順から戻る前にそれを呼び出します。
動的な初期設定は、ldd(1) では確認できません。 しかし、LD_DEBUG
環境変数を設定してトークン basicを含めることにより、実行時に初期設定呼び出しの正確な手順を確認できます。デバッギングライブラリを参照してください。
動的な初期設定を利用できるのは、遅延参照を処理する場合だけです。環境変数 LD_BIND_NOW
の使用、-z now オプションで構築されたオブジェクト、または RTLD_NOW モードを使用して dlopen(3DL) によって参照されたオブジェクトでは、あらゆる動的初期設定が迂回されます。
初期化が保留されて、dlopen(3DL) を使用して参照されたオブジェクトは、この関数から制御を返す前に初期化されます。
これまでは、ユーザーの期待に応えようとする方法で初期設定セクションと終了セクションを実行するさまざまな手法を説明してきました。しかし、依存関係同士の初期設定と終了の関係を単純化するためには、コーディングスタイルとリンク編集の助けも必要です。この単純化によって、初期設定と終了の処理を予測可能にし、不測の依存関係順序付けによる副次的作用を防止しやすくします。
初期設定セクションと終了セクションの内容は最小限に抑えてください。実行時にオブジェクトを初期化することによって、大域的なコンストラクタを避けてください。ほかの依存関係に対する初期設定および終了コードの依存を減らしてください。すべての動的オブジェクトについて依存関係の要件を定義してください。共有オブジェクト出力ファイルの生成を参照してください。不要な依存関係を定義しないでください。共有オブジェクトの処理を参照してください。依存関係の循環を避けてください。初期設定または終了の順序に頼らないでください。オブジェクトの順序は、共有オブジェクトとアプリケーションの開発によって変更される場合があるからです。依存関係の順序を参照してください。
セキュアプロセスには、その依存関係と「実行パス」を評価し、不当な依存関係の置換またはシンボルの割り込みを防ぐために使用されるいくつかの制約があります。
ユーザーがスーパーユーザーではなく、かつ実際のユーザー ID と有効なユーザー ID が異なる場合、実行時リンカーはプロセスをセキュアプロセスとして分類します。同様に、ユーザーがスーパーユーザーではなく、かつ実際のグループ ID と有効なグループ ID が異なる場合、プロセスはセキュアプロセスと見なされます。 getuid(2)、geteuid(2)、getgid(2)、および getegid(2) のマニュアルページを参照してください。
実行時リンカーが認識するデフォルトのトラストディレクトリは、32 ビットのオブジェクトの場合は /usr/lib/secure、64 ビットオブジェクトの場合は /usr/lib/secure/64 です。ユーティリティ crle(1) を使用すれば、セキュアアプリケーション向けに追加のトラストディレクトリを指定できます。この方法を使用する場合には、管理者は、ターゲットディレクトリを悪意のある侵入から適切に保護する必要があります。
LD_LIBRARY_PATH
ファミリの環境変数がセキュアプロセス用に有効である場合は、この変数によって指定されたトラストディレクトリのみが、実行時リンカーの検索規則を補強するために使用されます。実行時リンカーが検索するディレクトリを参照してください。
セキュアプロセスでは、「実行パス」がフルパスである場合は、アプリケーションまたはその依存関係によって指定された実行パスの指定が使用されます。つまり、パス名の冒頭部分に「/」が付く場合です。
セキュアプロセスでは、$ORIGIN 文字列の拡張は、それがトラストディレクトリに拡張されるときに限り許可されます。セキュリティ を参照してください。
セキュアプロセスでは、LD_CONFIG
は無視されます。セキュアプロセスは、デフォルト構成ファイルが存在する場合、この構成ファイルを使用します。crle(1) のマニュアルページを参照してください。
追加オブジェクトは、LD_PRELOAD
環境変数
、または LD_AUDIT
環境変数を使用したセキュアプロセスで読み込まれます。これらのオブジェクトはフルパス名または単純ファイル名で指定しなければなりません。フルパス名は、既知のトラストディレクトリに限定されます。単純ファイル名
(名前に「/」がついていない) は、前述した検索パスの制約に従って配置されます。 単純ファイル名は、既知のトラストディレクトリにのみ解決されることになります。
セキュアプロセスでは、単純ファイル名を構成する依存関係は、前述のパス名の制約を使用して処理されます。フルパス名または相対パス名で表示された依存関係は、そのまま使用されます。そのため、セキュアプロセスの開発者は、フルパス名または相対パス名の依存関係として参照されるターゲットディレクトリを、不当な侵入から確実に保護する必要があります。
セキュアプロセスを作成する場合には、依存関係の表示や、dlopen(3DL) パス名の構築に、相対パス名は使用しないことをお勧めします。この制約は、アプリケーションと依存関係すべてに適用されます。
アプリケーションのリンク編集中に指定された依存関係は、プロセスの初期設定中に実行時リンカーによって処理されます。このメカニズムに加えて、アプリケーションは、追加オブジェクトと結合することにより、その実行中にアドレススペースを拡張できます。アプリケーションは、リンク編集中に指定された依存関係の処理と同じ実行時リンカーのサービスを要求できます。
この遅延オブジェクトの結合処理には、いくつかの利点があります。
アプリケーションの初期設定中ではなく、オブジェクトが要求された時点でオブジェクトを処理することにより、起動時間を大幅に削減できる。実際、ヘルプや情報のデバッギングといったアプリケーションの特定の動作中に、そのサービスが必要とされない場合は、オブジェクトが要求されないことがある
アプリケーションは、ネットワーキングプロトコルなどの、必要なサービスによって決まる、いくつかの異なるオブジェクト間で選択される
実行時にオブジェクトに追加されたプロセスのアドレススペースは、使用後には解放される
アプリケーションは、次の典型的な手順を使用して、追加の共有オブジェクトにアクセスできます。
共有オブジェクトは、dlopen(3DL) を使用して実行中のアプリケーションのアドレススペースに配置され、追加される。この共有オブジェクトが所有する依存関係は、この時点で配置されて追加される
追加された共有オブジェクトとその依存関係は、再配置される。これらのオブジェクト内の初期設定セクションが呼び出される
アプリケーションは、追加されたオブジェクト内のシンボルを、dlsym(3DL) を使用して配置する。次に、アプリケーションはデータを参照するか、またはこの新しいシンボルによって定義された関数を呼び出す
オブジェクトによってアプリケーションが終了した後で、dlclose(3DL) を使用してアドレススペースを解放できる。解放されたオブジェクト内の終了セクションは、この時点で呼び出される
これらの実行時リンカーのインタフェースルーチンを使用した結果発生したエラー状態は、dlerror(3DL) を使用して表示できる
実行時リンカーのサービスは、ヘッダーファイル dlfcn.h 内に定義され、共有オブジェクト libdl.so.1 によってアプリケーションで使用できるようになります。次の例では、ファイル main.c は、ルーチンの dlopen(3DL) ファミリのどれでも参照でき、アプリケーション prog は、実行時にこれらのルーチンと結合できます。
$ cc -o prog main.c -ldl |
追加オブジェクトは、dlopen(3DL) を使用して、実行プロセスのアドレススペースに追加できます。この関数は、引数としてファイル名と結合モードを入手し、アプリケーションにハンドルを戻します。このハンドルを使用すると、アプリケーションは、dlsym(3DL) を使用することによってシンボルを配置できます。
パス名が、単純ファイル名で指定されている (名前の中に「/」が組み込まれていない) 場合、実行時リンカーは一連の規則を使用して、適切なパス名を生成します。「/」が組み込まれたパス名は、そのまま使用されます。
これらの検索パスの規則は、最初の依存関係の配置に使用された規則と全く同じものです。実行時リンカーが検索するディレクトリを参照してください。 たとえば、ファイル main.c は、以下のようなコードフラグメントが組み込まれているとします。
#include <stdio.h> #include <dlfcn.h> main(int argc, char ** argv) { void * handle; ..... if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) { (void) printf("dlopen: %s\n", dlerror()); exit (1); } ..... |
共有オブジェクト foo.so.1 を配置するために、実行時リンカーは、プロセスの初期設定時に表示された LD_LIBRARY_PATH
定義に、リンク編集 prog
中に指定された「実行パス」を続けます。デフォルトの位置として、/usr/lib
(32 ビットオブジェクトの場合) と /usr/lib/64 (64 ビットオブジェクトの場合) を使用します。
パス名が次のように指定されているとします。
if ((handle = dlopen("./foo.so.1", RTLD_LAZY)) == NULL) { |
実行時リンカーは、プロセスの現在の作業ディレクトリ内でこのファイルだけを検索します。
lopen(3DL) を使用して指定された共有オブジェクトは、そのバージョンのファイル名で参照することをお勧めします。バージョンについての詳細は、バージョン管理ファイル名の管理 を参照してください。
必要なオブジェクトが配置されていない場合は、dlopen(3DL) によって NULL ハンドルが戻されます。この場合、dlerror(3DL) を使用すると、失敗した真の理由を表示できます。次に例を示します。
$ cc -o prog main.c -ldl $ prog dlopen: ld.so.1: prog: fatal: foo.so.1: open failed: No such \ file or directory |
dlopen(3DL) によって追加されたオブジェクトに、他のオブジェクトに依存する関係がある場合、その依存関係もプロセスのアドレススペースに配置されます。このプロセスは、指定されたオブジェクトの依存関係がすべて読み込まれるまで継続されます。この依存関係のツリーを「グループ」と呼びます。
dlopen(3DL) によって指定されたオブジェクト、または依存関係がすでにプロセスイメージの一部である場合は、そのオブジェクトはこれ以上処理されません。この場合でも有効なハンドルは、アプリケーションに戻されます。このメカニズムにより、同じオブジェクトが複数回読み込まれることを防ぐことができます。また、このメカニズムを使用すると、アプリケーションは専用のハンドルを入手できます。たとえば、前述した main.c の例には、次のような dlopen() 呼び出しが組み込まれているとします。
if ((handle = dlopen((const char *)0, RTLD_LAZY)) == NULL) { |
この場合、dlopen(3DL) から戻されたハンドルは、アプリケーションそのものの中、プロセスの初期設定の一部として読み込まれた依存関係の中、または RTLD_GLOBAL フラグが指定された dlopen(3DL) を使用してプロセスのアドレススペースに追加されたオブジェクトの中のシンボルを配置できます。
第 3 章「実行時リンカー」で説明したように、実行時リンカーは、オブジェクトを配置して読み込んだ後、各オブジェクトを処理し、必要な再配置を実行する必要があります。dlopen(3DL) を使用してプロセスのアドレススペースに配置されたオブジェクトは、同じ方法で再配置する必要もあります。
単純なアプリケーションの場合には、このプロセスはそれほど重要な意味を持ちません。しかし、多数の dlopen(3DL) 呼び出しと、共通の依存関係も伴う複雑なアプリケーションを所有するユーザーにとって、このことは非常に重要です。
再配置は、実行された時間によって分類されます。実行時リンカーのデフォルトの動作では、初期設定時に即時参照の再配置がすべて処理され、プロセスの実行時に遅延参照がすべて処理されます。後者の処理は通常、遅延結合と呼ばれます。
この同じメカニズムは、モードが RTLD_LAZY として定義されているときに、dlopen(3DL) を使用して追加されたオブジェクトに適用されます。 この代わりとしては、オブジェクトが追加されたときに、オブジェクトの再配置すべてをすぐに実行する必要があります。これは、RTLD_NOW モードを使用することによって、またはリンカーの-z nowオプションを使用して作成されたときに、オブジェクト内のこの必要条件を記録することによって実現されます。この再配置の必要条件は、オープン状態のオブジェクトの依存関係に伝達されます。
また、再配置は、非シンボリックおよびシンボリックにも分類できます。このセクションの後半では、シンボル再配置がいつ発生するかに関係なく、この再配置に関連した問題について、シンボル検索の詳細に焦点をあてて説明します。
dlopen(3DL) によって取得したオブジェクトが大域シンボルを参照する場合は、実行時リンカーは、プロセスを作成したオブジェクトのプールからこのシンボルを配置する必要があります。直接結合がない場合は、dlopen(3DL) によって入手されたオブジェクトには、次の節で記述されているようにデフォルトのシンボル検索モデルが適用されます。 ただし、プロセスを作成したオブジェクトの属性と結合される dlopen(3DL) のモードは、代わりのシンボル検索のモデルに提供されます。
直接結合を指定されたオブジェクトでは、それに対応する依存関係から直接、シンボルが検索されます。ただし、この後で述べるすべての属性はそのまま有効です。直接結合を参照してください。
オブジェクトの 2 つの属性は、シンボル検索に影響を与えます。1 つ目は、オブジェクトシンボルの検索範囲の要求で、2 つ目は、プロセス内の各オブジェクトが提供するシンボルの可視性です。オブジェクトの検索範囲には次のものがあります。
オブジェクトは、プロセス内の他の大域オブジェクト内で検索されます。
オブジェクトは、同じグループ内のオブジェクト内でのみ検索されます。dlopen(3DL) を使用して入手されたオブジェクトから作成された依存関係ツリー、またはリンカーの -B group オプションを使用して構築されたオブジェクトから作成された依存関係ツリーは、固有のグループを形成します。
オブジェクトからのシンボルの可視性には、次のものがあります。
デフォルトにより、dlopen(3DL) を使用して入手したオブジェクトには、ワールドシンボル検索範囲とローカルシンボル可視性が割り当てられます。デフォルトのシンボル検索モデル では、このデフォルトモデルを使用して、典型的なオブジェクトグループのインタラクションについて説明しています。大域オブジェクトの定義、グループの分離、および オブジェクト階層では、デフォルトのシンボル検索の展開に dlopen(3DL) モードとファイル属性を使用する例を示しています。
dlopen(3DL) によって追加された各オブジェクトでは、実行時リンカーは、最初に動的実行プログラム内でシンボルを検索します。次に実行時リンカーは、プロセスの初期設定中に提供されたそれぞれのオブジェクト内を検索します。シンボルが検出されない場合には、実行時リンカーは、dlopen(3DL) によって入手されたオブジェクト内と、その依存関係内の検索を続行します。
次の例の動的実行プログラム prog と共有オブジェクト B.so.1 には、単純な依存関係が付いています。
$ ldd prog A.so.1 => ./A.so.1 $ ldd B.so.1 C.so.1 => ./C.so.1 |
prog が、dlopen(3DL) を使用して共有オブジェクト B.so.1 を入手した場合、共有オブジェクト B.so.1 と C.so.1 の再配置に必要なシンボルが、最初に prog 内で検索され、A.so.1、B.so.1、C.so.1 の順番に検索されます。このような単純なケースでは、dlopen(3DL) によって入手された共有オブジェクトは、アプリケーションの元のリンク編集の末尾に追加されたと考えます。たとえば、上記のオブジェクトの参照を図示すると、次のようになります。
dlopen(3DL) から入手されたオブジェクトによって要求されたシンボル検索は、影付きのブロックで示しています。このシンボル検索は、動的実行プログラム prog から、最後の共有オブジェクト C.so.1 へと進みます。
このシンボル検索は、読み込まれたオブジェクトに割り当てられた属性によって確立されます。動的実行プログラムとそれと同時に読み込まれたすべての依存関係には、大域シンボル可視性が割り当てられ、新しいオブジェクトにはワールドシンボルの検索範囲が割り当てられることを思い出してください。これによって、新しいオブジェクトは元のオブジェクト内を調べてシンボルを検索できます。また、新しいオブジェクトは、固有のグループを形成し、このグループ内では、各オブジェクトはローカルシンボル可視性を持ちます。そのため、グループ内の各オブジェクトは、他のグループ構成要素内でシンボルを検索できます。
これらの新しいオブジェクトは、アプリケーションまたはその最初のオブジェクトの依存関係によって要求される、通常のシンボル検索には影響を与えません。たとえば、上記の dlopen(3DL) が実行された後で、A.so.1 に関数再配置が必要な場合、実行時リンカーの再配置シンボルの通常の検索は、prog と A.so.1 で実施されます。B.so.1 または C.so.1 は検索されません。
このシンボル検索は、読み込まれたときにオブジェクトに割り当てられた属性によって実行されます。ワールドシンボルの検索範囲が、動的実行プログラムとこれとともに読み込まれた依存関係に割り当てられます。この検索範囲では、ローカルシンボル可視性だけを提供する新しいオブジェクト内を検索できません。
これらのシンボル検索とシンボル可視性の属性は、そのプロセスのアドレススペースへの投入とオブジェクト間の依存の関係に基づいて、オブジェクト間の関係を保持します。指定された dlopen(3DL) に関連したオブジェクトを割り当てることにより、固有のグループでは、同じ dlopen(3DL) と関連したオブジェクトだけが、グループ内のオブジェクトと関連する依存関係の中の検索ができます。
このオブジェクト間の関係を定義するという概念は、複数の dlopen(3DL) を実行するアプリケーション内では、より明確になります。たとえば、共有オブジェクト D.so.1 に次の依存関係があるとします。
$ ldd D.so.1 E.so.1 => ./E.so.1 |
このとき、prog アプリケーションが、共有オブジェクト B.so.1 に加えて、この共有オブジェクトにも dlopen(3DL) を実行したとします。次の図は、オブジェクト間のシンボル検索の関係を示しています。
B.so.1 と D.so.1 の両方にシンボル foo の定義が組み込まれ、C.so.1 と E.so.1 にこのシンボルを必要とする再配置が組み込まれているとします。 固有のグループに対するオブジェクトの関係によって、C.so.1 は B.so.1 の定義に結合され、E.so.1 は D.so.1 の定義に結合されます。このメカニズムは、dlopen(3DL) への複数の呼び出しにより入手されたオブジェクトの最も直感的な結合を提供するためのものです。
オブジェクトが、前述した処理の進行の中で使用される場合、それぞれの dlopen(3DL) が実施された順番は、結果として発生するシンボル結合には影響しません。ただし、複数のオブジェクトに共通の依存関係がある場合は、結果の結び付きは、dlopen(3DL)呼び出しが実行された順番による影響を受けます。
次に、同じ共通依存関係を持つ共有オブジェクト O.so.1 と P.so.1 の例を示します。
$ ldd O.so.1 Z.so.1 => ./Z.so.1 $ ldd P.so.1 Z.so.1 => ./Z.so.1 |
この例では、prog アプリケーションは、各共有オブジェクトに dlopen(3DL) を使用しています。共有オブジェクト Z.so.1 が、O.so.1 と P.so.1 両方の共通依存関係であるため、 この依存関係は 2 つの dlopen(3DL) 呼び出しに関連する両方のグループに割り当てられます。この依存関係を次の図に示します。
この結果、O.so.1 と P.so.1 の両方がシンボルの検索に Z.so.1 を使用できます。ここで重要なのは、dlopen(3DL)の順序に限って言えば、Z.so.1 も O.so.1 と P.so.1 の両方の中でシンボルを検索できることです。
そのため、O.so.1 と P.so.1 の両方に、Z.so.1 の再配置に必要なシンボル foo の定義が組み込まれている場合、実際に発生する結び付きを予期することはできません。それは、この結び付きが dlopen(3DL) 呼び出しの順序の影響を受けるからです。シンボル foo の機能が、シンボルが定義されている 2 つの共有オブジェクト間で異なる場合、Z.so.1 コードを実行したすべての結果は、アプリケーションの dlopen(3DL) の順序によって異なる可能性があります。
dlopen(3DL) によって入手されたオブジェクトへのデフォルトのローカルシンボルの可視性の割り当ては、モード引数に RTLD_GLOBAL フラグを指定することによって、大域に拡大ができます。このモードでは、dlopen(3DL) によって入手されたオブジェクトは、シンボルを配置するためのワールドシンボルの検索範囲の指定された他のオブジェクトによって使用することができます。
また、RTLD_GLOBAL フラグが指定された dlopen(3DL) によって入手されたオブジェクトは、dlopen() (値 0 のパス名を指定) を使用したシンボル検索にも使用できます。
ローカルシンボルの可視性を持つグループの構成要素が、他の大域シンボルの可視性を必要とするグループによって参照される場合、オブジェクトの可視性はローカルと大域の両方を連結したものになります。この後大域グループの参照が削除されても、この格上げされた属性はそのまま残ります。
dlopen(3DL) によって入手されたオブジェクトへのデフォルトのワールドシンボルの検索範囲の割り当ては、モード引数に RTLD_GROUP フラグを指定することによって、グループに縮小することができます。このモードでは、dlopen(3DL) によって入手されたオブジェクトは、そのオブジェクト固有のグループ内でしかシンボルの検索ができません。
リンカーの -B group オプションを使用して構築したオブジェクトには、グループのシンボル検索範囲を割り当てることができます。
グループ検索機能を持つグループの構成要素が、ワールド検索機能を必要とする他のグループによって参照された場合、オブジェクトの検索機能はグループとワールドが結合したものになります。この後ワールドグループの参照が削除されても、この格上げされた属性はそのまま残ります。
dlopen(3DL) によって入手された最初のオブジェクトが、2 番目のオブジェクトに dlopen(3DL) を使用した場合、両方のオブジェクトは 1 つのグループに割り当てられます。これにより、オブジェクトが互いにシンボルを配置し合うことを防ぐことができます。
実装の中には、最初のオブジェクトの場合、シンボルを 2 番目のオブジェクトの再配置用にエクスポートする必要がある場合もあります。この必要条件は、次の 2 つのメカニズムのいずれかによって満たすことができます。
最初のオブジェクトを 2 番目のオブジェクトの明示的な依存関係にした場合、これは 2 番目のオブジェクトのグループにも割り当てられます。そのため、最初のオブジェクトは、2 番目のオブジェクトの再配置に必要なシンボルも提供できます。
ほとんどのオブジェクトが 2 番目のオブジェクトに dlopen(3DL) を実行し、これらの最初のオブジェクトが、それぞれ 2 番目のオブジェクトの再配置を満足させる同じシンボルをエクスポートする必要がある場合、2 番目のオブジェクトには明示的な依存関係を割り当てることはできません。この場合、2 番目のオブジェクトの dlopen(3DL) モードは、RTLD_PARENT フラグを使用して補強できます。このフラグによって、2 番目のオブジェクトのグループが、明示的な依存関係が伝達されたのと同じ方法で、最初のオブジェクトに伝達されます。
上記の 2 つのテクニックには、1 つ異なる点があります。明示的な依存関係を指定する場合、その依存関係そのものは、2 番目のオブジェクトの dlopen(3DL) 依存関係ツリーの一部になるため、dlsym(3DL) を使用したシンボル検索が可能になります。RTLD_PARENT を使用して 2 番目のオブジェクトを入手する場合、最初のオブジェクトは、dlopen(3DL) を使用したシンボルの検索に使用できるようにはなりません。
dlopen(3DL) によって 2 番目のオブジェクトが、大域シンボル可視性が指定された最初のオブジェクトから入手された場合、RTLD_PARENT モードは冗長で他に影響を与えることはありません。このような状態は、dlopen(3DL) がアプリケーションから呼び出されたとき、またはアプリケーションの中の依存関係の 1 つから呼び出されたときに多く発生します。
プロセスは、dlsym(3DL) を使用して特定のシンボルのアドレスを入手できます。この関数は、ハンドルとシンボルをとり、呼び出し元にそのシンボルのアドレスを戻します。ハンドルは、次の方法でシンボルの検索を指示します。
dlopen(3DL) にオブジェクトを指定して戻されるハンドル。このハンドルを使用すると、指定したオブジェクトとその依存ツリーを構成するオブジェクト群からシンボルを入手できる。RTLD_FIRST モードを使用して戻されたハンドルの場合は、指定したオブジェクトだけからシンボルを入手できる
dlopen(3DL) にパス名の値として 0 を指定して戻されるハンドル。このハンドルを使用すると、関連づけられたリンクマップの開始オブジェクトと、その依存ツリーを構成するオブジェクト群から、シンボルを入手できる。通常、開始オブジェクトは動的実行可能ファイルである。このハンドルを使用すると、関連づけられたリンクマップ上の、dlopen(3DL) で RTLD_GLOBAL モードを使用して読み込まれたすべてのオブジェクトからも、シンボルを入手できる。RTLD_FIRST モードを使用して戻されたハンドルの場合は、関連づけられたリンクマップ上の開始オブジェクトだけからシンボルを入手できる
特別なハンドル RTLD_DEFAULT を使用すると、関連づけられたリンクマップの開始オブジェクトと、その依存ツリーを構成するオブジェクト群からシンボルを入手できる。このハンドルを使用すると、呼び出し元と同じグループに属する、dlopen(3DL) を使用して読み込まれたオブジェクトからも、シンボルを入手できる。RTLD_DEFAULT の使用は、呼び出し元オブジェクトからシンボル再配置を解決するために使用されるモデルと、同じモデルに従う
特別なハンドル RTLD_NEXT を使用すると、呼び出し元のリンクマップリスト上に存在する次の関連オブジェクトからシンボルを入手できる
次に、一般的なケースを示します。この例では、アプリケーションはそのアドレス空間に追加オブジェクトを追加します。続いてアプリケーションは、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)); } |
シンボル foo と bar は、ファイル foo.so.1 内で検索された後で、このファイルに関連した依存関係が検索されます。次に、関数 foo は、単一の引数 bar によって return() ステートメントの一部として呼び出されます。
上記のファイル main.c を使用して構築されたアプリケーション prog には、次のような依存関係があります。
$ ldd prog libdl.so.1 => /usr/lib/libdl.so.1 libc.so.1 => /usr/lib/libc.so.1 |
dlopen(3DL) 内に指定されたファイル名に値 0 がある場合、シンボル foo と bar は、prog、/usr/lib/libdl.so.1、/usr/lib/libc.so.1 の順番で検索されます。
ハンドルがシンボル検索を開始するルートを指示している場合は、この検索メカニズムは、シンボルの検索 で説明したものと同じモデルに従います。
要求されたシンボルが配置されていない場合は、dlsym(3DL) は、NULL 値を戻します。この場合、dlerror(3DL) を使用すると、失敗の真の理由を示すことができます。次の例では、アプリケーション prog はシンボル bar を配置できません。
$ prog dlsym: ld.so.1: main: fatal: bar: can't find symbol |
特別なハンドル 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–1 の例で示すようにファイル B.so.1 に組み込まれていた場合、foo の検索は B.so.1 と C.so.1 でも継続して行われます。
このメカニズムによって、ウィークシンボルで説明した定義されていないウィーク参照の代わりに使用できる、パワフルで柔軟性のある代替機能が提供されます。
特別なハンドル 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–1 の例で示すように、ファイル 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 の前に割り込ませることができます。こうすれば、malloc() に対するすべての呼び出しは、本来の関数が呼ばれて割り当てを行う前に、割り込まれます。
$ 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 を使用することにより、予測可能で有用な割り込みテクニックが使用できます。ただし、このテクニックを汎用オブジェクトの依存関係内で使用する場合には、実際に読み込まれる順番が必ず予測できるとは限らないため、注意が必要です。
リンカーによって構築された動的オブジェクトは、新しい実行時リンカー機能を要求することがあります。関数 _check_rtld_feature() を使用して、実行に必要な実行時機能が実行時リンカーによってサポートされているかどうかを確認できます。現在チェックされる機能については、表 7–46 を参照してください。
Solaris リンカーには、デバッギングライブラリと mdb(1) モジュールが組み込まれています。デバッギングライブラリを使用すると、実行時のリンクプロセスをより詳細に監視できます。mdb(1) モジュールを使用すると、プロセスのデバッグを対話形式で行うことができます。
デバッギングライブラリは、アプリケーションと依存関係の実行を理解したり、デバッグする場合に役立ちます。このライブラリを使用して表示される情報のタイプは、定数のままであると予期されますが、この情報の正確な形式は、リリースごとにわずかに変更される場合があります。
実行時リンカーをよく理解していないと、デバッギング出力のなかには理解できないものがある可能性があります。しかし、一般的には、興味深い面が多いといえます。
デバッギングは、環境変数 LD_DEBUG
を使用して実行します。すべてのデバッギングの出力は、接頭辞としてプロセス識別子を持っていて、デフォルトごとに、標準的なエラーに対して送信されます。この環境変数は、1
つまたは複数のトークンを使用して、必要なデバッギングタイプを示す必要があります。
このデバッギングオプションとともに使用できるトークンは、LD_DEBUG=help
を使って表示できます。どの動的実行プログラムを使用しても、この情報を要求することができます。この場合、プロセス自体が終了した後でこの情報が表示されます。次に例を示します。
$ LD_DEBUG=help prog 11693: 11693: For debugging the runtime linking of an application: 11693: LD_DEBUG=token1,token2 prog 11693: enables diagnostics to the stderr. The additional 11693: option: 11693: LD_DEBUG_OUTPUT=file 11693: redirects the diagnostics to an output file created 11593: using the specified name and the process id as a 11693: suffix. All diagnostics are prepended with the 11693: process id. 11693: 11693: 11693: audit display runtime link-audit processing 11693: basic provide basic trace information/warnings 11693: bindings display symbol binding; detail flag shows 11693: absolute:relative addresses 11693: detail provide more information in conjunction with other 11693: options 11693: files display input file processing (files and libraries) 11693: help display this help message 11693: libs display library search paths 11693: move display move section processing 11693: reloc display relocation processing 11693: symbols display symbol table processing 11693: tls display TLS processing info 11693: unused display unused/unreferenced files 11693: versions display version processing |
この例では、実行時リンカーに有効なオプションを示しています。正確なオプションについては、リリースごとに異なる場合があります。
環境変数 LD_DEBUG_OUTPUT
を使用すると、出力ファイルを標準エラーの代わりに使用するように指定できます。出力ファイルの名前には、接尾辞としてプロセス
ID が付きます。
セキュアアプリケーションのデバッギングは実行できません。
実行時に発生するシンボル結合の表示機能は、最も有効なデバッギングオプションの 1 つです。次の例では、2 つのローカル共有オブジェクト上に依存関係を持つ、非常に単純な動的実行プログラムを取り上げてみます。
$ cat bar.c int bar = 10; $ cc -o bar.so.1 -K pic -G bar.c $ cat foo.c foo(int data) { return (data); } $ cc -o foo.so.1 -K pic -G foo.c $ cat main.c extern int foo(); extern int bar; main() { return (foo(bar)); } $ cc -o prog main.c -R/tmp:. foo.so.1 bar.so.1 |
実行時シンボルは、LD_DEBUG=bindings
を設定することによって表示されます。
$ LD_DEBUG=bindings prog 11753: ....... 11753: binding file=prog to file=./bar.so.1: symbol bar 11753: ....... 11753: transferring control: prog 11753: ....... 11753: binding file=prog to file=./foo.so.1: symbol foo 11753: ....... |
即時再配置で要求されたシンボル bar が、アプリケーションが制御を受け取る前に結合されます。これに対して、遅延再配置で要求されたシンボル foo は、アプリケーションが制御を受け取った後、関数が最初に呼び出されたときに結合されます。これは、遅延結合のデフォルトモードを示しています。環境変数 LD_BIND_NOW
が設定されている場合、シンボル結合はすべて、アプリケーションが制御を受け取る前に実行されます。
LD_DEBUG=bindings,detail
を設定すると、実際の結合位置の実アドレスと相対アドレスに関する情報が出力されます。
実行時リンカーが、関数の再配置を実行すると、関数 .plt に関連したデータも書き換えられるため、この後に発生する呼び出しは、関数に直接送信されます。環境変数 LD_BIND_NOT
は、あらゆる値に設定されて、このデータの更新を防ぐことができます。この変数を、詳細結合に対するデバッギング要求とともに使用すると、関数結合すべての完全な実行時アカウントを入手できます。ただし、実行結果が大量に出力されることがあり、その場合、アプリケーションのパフォーマンスが低下します。
LD_DEBUG
を使用すれば、検索パスの使用状況を表示できます。たとえば、依存関係の配置に使用される検索パスのメカニズムは、次のように LD_DEBUG=libs
を設定して表示できます。
$ LD_DEBUG=libs prog 11775: 11775: find object=foo.so.1; searching 11775: search path=/tmp:. (RPATH from file prog) 11775: trying path=/tmp/foo.so.1 11775: trying path=./foo.so.1 11775: 11775: find object=bar.so.1; searching 11775: search path=/tmp:. (RPATH from file prog) 11775: trying path=/tmp/bar.so.1 11775: trying path=./bar.so.1 11775: ....... |
アプリケーション prog 内に記録された「実行パス」は、2 つの依存関係 foo.so.1 と bar.so.1 の検索に影響を与えます。
これと同様の方法で、各シンボルを検索する検索パスは、LD_DEBUG=symbols
を設定して表示できます。これを bindings
要求と結合すると、次のように、シンボル再配置プロセスが完全に表示されます。
$ LD_DEBUG=bindings,symbols 11782: ....... 11782: symbol=bar; lookup in file=./foo.so.1 [ ELF ] 11782: symbol=bar; lookup in file=./bar.so.1 [ ELF ] 11782: binding file=prog to file=./bar.so.1: symbol bar 11782: ....... 11782: transferring control: prog 11782: ....... 11782: symbol=foo; lookup in file=prog [ ELF ] 11782: symbol=foo; lookup in file=./foo.so.1 [ ELF ] 11782: binding file=prog to file=./foo.so.1: symbol foo 11782: ....... |
上記の例では、シンボル bar は、アプリケーション prog 内では検索されません。これは、コピーの再配置を処理するときに行なわれる最適化が原因です。この再配置タイプの詳細については、コピー再配置を参照してください。
デバッガモジュールは、mdb(1) に読み込むことができる一群の dcmd および walker を提供します。デバッガモジュールを使用すると、実行時リンカーのさまざまな内部データ構造を検査できます。実行時リンカーの内部データ構造を理解するには、実行時リンカー内部に関する知識が必要であり、また、これはリリースごとに異なる可能性があります。しかし、これらの情報は、動的にリンクされたプロセスの基本的な構成要素を明らかにし、さまざまなデバッグを助けます。
次の例に、mdb(1) と実行時リンカーのデバッガモジュールを使用したいくつかのシナリオを示します。
$ cat main.c #include <dlfnc.h> int main() { void * handle; void (* fptr)(); if ((handle = dlopen("foo.so.1", RTLD_LAZY)) == NULL) return (1); if ((fptr = (void (*)())dlsym(handle, "foo")) == NULL) return (1); (*fptr)(); return (0); } $ cc -o main main.c -R. -ldl |
mdb(1) がデバッガモジュール ld.so を自動的に読み込まなかった場合は、明示的に読み込んでください。それにより、デバッガモジュールの機能を確認できます。
$ mdb main > ::load ld.so > ::dmods -l ld.so ld.so ----------------------------------------------------------------- dcmd Bind - Display a Binding descriptor dcmd Callers - Display Rt_map CALLERS binding descriptors dcmd Depends - Display Rt_map DEPENDS binding descriptors dcmd ElfDyn - Display Elf_Dyn entry dcmd ElfEhdr - Display Elf_Ehdr entry dcmd ElfPhdr - Display Elf_Phdr entry dcmd Groups - Display Rt_map GROUPS group handles dcmd GrpDesc - Display a Group Descriptor dcmd GrpHdl - Display a Group Handle dcmd Handles - Display Rt_map HANDLES group descriptors dcmd List - Display entries in a List dcmd ListRtmap - Display a List of Rt_Map's dcmd Lm_list - Display ld.so.1 Lm_list structure dcmd Rt_map - Display ld.so.1 Rt_map structure dcmd Rt_maps - Display list of Rt_map structures walk List - Walk a List walk Rt_maps - Walk a List of Rt_maps > ::bp main > :r |
プロセス内の動的オブジェクトは、リンクマップ Rt_map として表現され、このリンクマップは、リンクマップリスト上で管理されています。プロセスのすべてのリンクマップは、Rt_maps を使用して表示できます。
> ::Rt_maps Link-map lists (dynlm_list): 0xffbfe0d0 ---------------------------------------------- Lm_list: 0xff3f6f60 (LM_ID_BASE) ---------------------------------------------- Link-map* ADDR() NAME() ---------------------------------------------- 0xff3f9040 0x00010000 main 0xff3f9460 0xff3b0000 /lib/libdl.so.1 0xff3f977c 0xff280000 /lib/libc.so.1 ---------------------------------------------- Lm_list: 0xff3f6f88 (LM_ID_LDSO) ---------------------------------------------- 0xff3f8cc0 0xff3c0000 /lib/ld.so.1 |
個々のリンクマップは、Rt_map を使用して表示できます。
> 0xff3f9040::Rt_map Rt_map located at: 0xff3f9040 NAME: main ADDR: 0x00010000 DYN: 0x000207bc NEXT: 0xff3f9460 PREV: 0x00000000 FCT: 0xff3f6f18 TLSMODID: 0 INIT: 0x00010710 FINI: 0x0001071c GROUPS: 0x00000000 HANDLES: 0x00000000 DEPENDS: 0xff3f96e8 CALLERS: 0x00000000 ..... |
オブジェクトの .dynamic セクションは、ElfDyn dcmd を使用して表示できます。次の例は、最初の 4 つのエントリを表示しています。
> 0x000207bc,4::ElfDyn Elf_Dyn located at: 0x207bc 0x207bc NEEDED 0x0000010f Elf_Dyn located at: 0x207c4 0x207c4 NEEDED 0x00000124 Elf_Dyn located at: 0x207cc 0x207cc INIT 0x00010710 Elf_Dyn located at: 0x207d4 0x207d4 FINI 0x0001071c |
mdb(1) は、遅延ブレークポイントを設定するときにとても有用です。次の例では、関数 foo() にブレークポイントを設定しています。 foo.so.1 に対して dlopen(3DL) が実行されるまでは、このシンボルはデバッガにとって未知であるにもかかわらず、遅延ブレークポイントを設定すると、動的オブジェクトが読み込まれたときに、実ブレークポイントが設定されます。
> ::bp foo.so.1`foo > :c > mdb: You've got symbols! > mdb: stop at foo.so.1`foo mdb: target stopped at: foo.so.1`foo: save %sp, -0x68, %sp |
この時点で、新しいオブジェクトが読み込まれました。
> *ld.so`lml_main::Rt_maps Link-map* ADDR() NAME() ---------------------------------------------- 0xff3f9040 0x00010000 main 0xff3f9460 0xff3b0000 /lib/libdl.so.1 0xff3f977c 0xff280000 /lib/libc.so.1 0xff3f9ca4 0xff380000 ./foo.so.1 0xff37006c 0xff260000 ./bar.so.1 |
foo.so.1 のリンクマップは、dlopen(3DL) から返されたハンドルを示しています。Handles を使用すると、ハンドルの構造体を展開できます。
> 0xff3f9ca4::Handles -v HANDLES for ./foo.so.1 ---------------------------------------------- HANDLE: 0xff3f9f60 Alist[used 1: total 1] ---------------------------------------------- Group Handle located at: 0xff3f9f28 ---------------------------------------------- owner: ./foo.so.1 flags: 0x00000000 [ 0 ] refcnt: 1 depends: 0xff3f9fa0 Alist[used 2: total 4] ---------------------------------------------- Group Descriptor located at: 0xff3f9fac depend: 0xff3f9ca4 ./foo.so.1 flags: 0x00000003 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ] ---------------------------------------------- Group Descriptor located at: 0xff3f9fd8 depend: 0xff37006c ./bar.so.1 flags: 0x00000003 [ AVAIL-TO-DLSYM,ADD-DEPENDENCIES ] |
ハンドルの依存関係は、dlsym(3DL) 要求を満たすハンドルのオブジェクトを表現するリンクマップのリストです。この例では、依存関係は foo.so.1 と bar.so.1 です。
上の例は、デバッガモジュールの機能の基礎的な紹介になっていますが、正確なコマンド、使用方法、および出力は、リリースごとに異なる可能性があります。お使いのシステムで利用できる正確な機能については、それぞれのマニュアルまたはヘルプを参照してください。