第 3 章「実行時リンカー」で説明したように、実行時リンカーは、メモリへのオブジェクトの割り当てや記号の結合を含む多数の操作を実行します。これらの操作を同じプロセス内から監視、および場合によっては変更すると強力なプロセス監視ツールになります。
このセクションでは、プロセスに関する実行時リンク情報にアクセスするために、そのプロセスにサポートされているインタフェースについて説明します。このインタフェースは rtld-監査インタフェースと呼ばれます。この機構の使用例としては、「共有オブジェクトのプロファイリング」 で説明されている共有オブジェクトの実行時プロファイルがあります。
rtld-監査インタフェースは、1 つまたは複数の監査インタフェースルーチンを提供する監査ライブラリとして実装されます。このライブラリがプロセスの一部として読み込まれている場合は、プロセス実行の各段階で、実行時リンカーによって監査ルーチンが呼び出されます。これらのインタフェースを使用すると、監査ライブラリは次のものにアクセスできます。
読み込まれているオブジェクトに関する情報
読み込まれているこれらのオブジェクト間で発生するシンボル結合。これらの結合は、監査ライブラリによって変更できる
関数呼び出しとその戻り値の監査を可能にするために、手続きリンカーテーブルエントリ (「手続きリンクテーブル (プロセッサに固有)」を参照) によって提供されるレイジー結合機構の開発。関数の引数とその戻り値は、監査ライブラリによって変更できる
これらの機能のいくつかは、特殊な共有オブジェクトを事前に読み込むことによって実現できます (「追加オブジェクトの読み込み」 を参照)。ただし、事前に読み込まれたオブジェクトは、プロセスのオブジェクトと同じ名前空間内にあります。このことは、通常、事前に読み込まれた共有オブジェクトの実装を制限したり、複雑化したりします。rtld-監査インタフェースは、ユーザーに対して、監査ライブラリを実行するための固有の名前空間を提供します。 これにより、監査ライブラリがプロセス内で発生する通常の結合を妨害することはなくなります。
実行時リンカーは、動的実行可能なプログラムをその依存関係と結合すると、リンクマップのリンクリストを構築して、プロセスを記述します。リンクマップ構造は、プロセス内の各オブジェクトを記述し、/usr/include/sys/link.h に定義されます。アプリケーションのオブジェクトを結合するために必要な記号検索機構は、このリンクマップリストを検索します。このリンクマップリストは、プロセス記号解決用の名前空間を提供します。
実行時リンカー自体もリンクマップによって記述されると、このリンクマップは、アプリケーションオブジェクトのリストとは異なるリストに維持されます。この結果、実行時リンカーは各自の固有名前空間に常駐することなるため、実行時リンカー内のサービスにアプリケーションが直接結合されることはなくなります (アプリケーションは、フィルタ libdl.so.1 を介してのみ、実行時リンカーの公共サービスを要求できます)。
rtld-監査インタフェースは、すべての監査ライブラリを保持するための各自のリンクマップリストを使用します。この結果、監査ライブラリは、アプリケーションの記号結合条件から分離されます。アプリケーションリンクマップリストの検査は、dlmopen(3X) によって実行できます。これは、RTLD_NOLOAD フラグとともに使用すると、監査ライブラリで、オブジェクトを読み込むことなくその存在を照会することができます。
アプリケーションと実行時リンカーのリンクマップリストを定義するために、2 つの識別子が /usr/include/link.h に定義されています。
#define LM_ID_BASE 0 /* application link-map list */ #define LM_ID_LDSO 1 /* runtime linker link-map list */ |
各 rtld-監査サポートライブラリには、固有の空きリンクマップ識別子が割り当てられています。
監査ライブラリは他の共有オブジェクトと同様に構築されますが、プロセス内の固有名前空間には、いくつかの注意が必要です。
すべての依存関係の条件を提供しなければならない
プロセス内のインタフェースに複数のインスタンスを提供しないシステムインタフェースは、使用できない
監査ライブラリが printf(3C) を呼び出す場合、その監査ライブラリは、libc への依存関係を定義しなければなりません (「共有オブジェクトの生成」を参照)。監査ライブラリには、固有の名前空間があるため、監査中のアプリケーションに存在する libc によって記号参照を満たすことはできません。監査ライブラリに libc への依存関係がある場合は、2 つのバージョンの libc.so.1 がプロセスに読み込まれます。1 つはアプリケーションのリンクマップリストの結合条件を満たし、もう 1 つは監査リンクマップリストの結合条件を満たします。
すべての依存関係が記録された状態で監査ライブラリが構築されるようにするには、リンカーの -z defs オプションを使用します (「共有オブジェクトの生成」を参照)。
システムインタフェースには、プロセスにおける実装状態の唯一のインスタンスであると想定して存在するものもあります。たとえば、スレッド、シグナル、および malloc(3C) などです。このようなインタフェースを使用すると、アプリケーションの動作が不正に変更されるおそれがあるため、監査ライブラリでは、このようなインタフェースの使用を避ける必要があります。
mapmalloc(3X) を使用した監査ライブラリによるメモリ割り当ては受け入れられます。これは、アプリケーションによって通常使用される割り当てスキーマとこの割り当てが共存可能なためです。
rtld_audit インタフェースは、実行時リンカー環境変数 LD_AUDIT
によって有効になります。 この環境変数は、dlopen(3X) によって読み込まれる共有オブジェクトをコロンで区切ったリストに設定されます。各オブジェクトは、各自の監査リンクマップリストに読み込まれます。また、各オブジェクトは、dlsym(3X) によって、監査ルーチンがないか検索されます。検出された監査ルーチンは、アプリケーション実行中に各段階で呼び出されます。
rtld_audit インタフェースを使用すると、複数の監査ライブラリを与えることができます。この方法で使用される監査ライブラリは、通常実行時リンカーによって返される結合を変更することはできません。もし変更すると、後に続く監査ライブラリで予期しない結果が生じます。
安全なアプリケーション (「セキュリティ」を参照) は、トラステッドディレクトリから監査ライブラリだけを取得できます。現在監査ライブラリに使用できるトラステッドディレクトリは、32 ビット実行可能プログラムの場合は /usr/lib と /usr/ccs/lib、64 ビット SPARCV9 実行可能プログラムの場合は /usr/lib/sparcv9 だけです。
次の関数が rtld-監査インタフェースによって提供されており、予定の使用順序で記述されます。
uint_t la_version(uint_t version); |
この関数は、実行時リンカーと監査ライブラリの間に初期接続を提供します。このインタフェースを読み込むには、監査ライブラリによってこれを提供する必要があります。
実行時リンカーは、サポート可能な最上位バージョンの rtld-監査によって、このインタフェースを呼び出します。監査ライブラリは、このバージョンが十分に使用できるかどうかを確認して、使用する予定のバージョンを返すことができます。このバージョンは、通常、/usr/include/link.h に定義されている LAV_CURRENT です。
監査ライブラリがゼロのバージョン、または実行時リンカーがサポートする rtld-監査インタフェースよりも大きい値を返す場合は、監査ライブラリは使用されません。
uint_t la_objopen(Link_map * lmp, Lmid_t lmid, uintptr_t * cookie); |
この関数は、新しいオブジェクトが実行時リンカーによって読み込まれるたびに呼び出されます。
lmp は、新しいオブジェクトを記述するリンクマップ構造を提供します。lmid は、オブジェクトが追加されているリンクマップリストを特定します (「名前空間の確立」 を参照)。cookie は、識別子へのポインタを提供します。この識別子は、オブジェクト lmp に初期設定されますが、監査ライブラリによって、オブジェクトを他の rtld-監査インタフェースルーチンに対して特定するように変更できます。
この関数は、このオブジェクトで問題になるシンボル結合を示す値を返します。この結果、la_symbind32/usr/include/link.h() に定義された次の値のマスクです。
LA_FLG_BINDTO - このオブジェクトに対する監査シンボル結合
LA_FLG_BINDFROM - このオブジェクトからの監査シンボル結合
これらの 2 つのフラグ使用法については、la_symbind() を参照してください。
ゼロの戻り値は、結合情報がこのオブジェクトで問題にならないことを示します。
void la_preinit(uintptr_t * cookie); |
この関数は、すべてのオブジェクトがアプリケーションに読み込まれた後で、アプリケーションへの制御の譲渡が発生する前に一度呼び出されます。
cookie は、プロセスを開始したプライマリオブジェクト、通常は動的実行可能プログラムを表わします。
uintptr_t la_symbind32(Elf32_Sym * sym, uint_t ndx, uintptr_t * refcook, uintptr_t * defcook, uint_t * flags); uintptr_t la_symbind64(Elf64_Sym * sym, uint_t ndx, uintptr_t * refcook, uintptr_t * defcook, uint_t * flags, const char * sym_name); |
この関数は、結合通知のタグが付けられた 2 つのオブジェクト間で結合が発生すると呼び出されます (la_objopen()を参照)。
sym は、構築された記号構造 (/usr/include/sys/elf.h を参照) であり、sym->st_value は結合中の記号定義のアドレスを示します。la_symbind32() は、sym->st_name を調整して実際の記号名を指していますが、la_symbind64() は sym->st_name を調整していません。これは、文字列テーブルのインデックスです。
ndx は、結合オブジェクト動的記号テーブル内の記号インデックスを示します。refcook は、この記号への参照を行うオブジェクトを記述します。この識別子は、LA_FLG_BINDFROM を返した la_objopen() に渡されたものと同じです。defcook は、この記号を定義するオブジェクトを記述します。この識別子は、LA_FLG_BINDTO を返した la_objopen() に渡されるものと同じです。
flags は、手続きリンクテーブル記号エントリの連続監査を変更するために使用できるデータ項目を指します。この値は、/usr/include/link.h に定義された次のフラグのマスクです。
sym_name は、結合中の記号の文字列名を指します (la_symbind64 の場合のみ)。
LA_SYMB_NOPLTENTER - la_*_pltenter() 関数は、この記号に対しては呼び出されない
LA_SYMB_NOPLTEXIT - la_pltexit() 関数は、この記号に対しては呼び出されない
LA_SYMB_DLSYM - dlsym(3X) を呼び出した結果発生したシンボル結合
LA_SYMB_ALTVALUE (LAV_VERSION2) -「la_symbind32/la_symbind64」への以前の呼び出しによって、記号値に対して代替値が返される
デフォルトにより、la_*_pltenter() または la_pltexit() 関数が監査ライブラリ内に存在する場合、シンボルが参照されるたびに、これらは手続きリンクテーブル記号に対して、la_symbind32 の後で呼び出されます (「監査インタフェースの制限」も参照)。
戻り値は、この呼び出しに続いて制御を渡す必要があるアドレスを示します。シンボル結合を監視するだけの監査ライブラリは、sym->st_value の値を返すため、制御は結合記号定義に渡されます。監査ライブラリは、異なる値を返すことによって、シンボル結合を意図的にリダイレクトできます。
la_symbind64() への sym_name は、処理されるシンボルの名前を含みます。これは、32 ビットインタフェースから sym->st_name フィールドで使用できます。
uint_t la_sparcv8_pltenter(Elf32_Sym * sym, uint_t ndx, uintptr_t * refcook, uintptr_t * defcook, La_sparcv8_regs * regs, uint_t * flags); uint_t la_sparcv9_pltenter(Elf64_Sym * sym, uint_t ndx, uintptr_t * refcook, uintptr_t * defcook, La_sparcv9_regs * regs, uint_t * flags, const char * sym_name); uint_t la_i86_pltenter(Elf32_Sym * sym, uint_t ndx, uintptr_t * refcook, uintptr_t * defcook, La_i86_regs * regs, uint_t * flags); |
これらの関数は、結合通知のタグが付けられた 2 つのオブジェクト間の手続きリンクシンボルエントリが呼び出されると、SPARC および x86 のシステムでそれぞれ呼び出されます (la_objopen() と la_symbind32() を参照)。
sym、ndx、refcook、defcook、およびsym_nameは、la_symbind32()/la_symbind64() に渡されたものと同じ情報を提供します。
regs は、/usr/include/link.h に定義されているように、SPARC システム上のout レジスタと、x86 システム上のstack およびframe レジスタを指します。
flags は、手続きリンクテーブルエントリの連続監査を変更するために使用できるデータ項目を指します。このデータ項目は、la_symbind32() から flags によって指されるものと同じです。この値は、/usr/include/link.h に定義された次のフラグのマスクです。
LA_SYMB_NOPLTENTER - la_sparcv8_pltenter() または la_i86_pltenter() 関数は、この記号では続いて呼び出されない
LA_SYMB_NOPLTEXIT - la_pltexit() 関数は、この記号では呼び出されない
戻り値は、この呼び出しに続いて制御を渡す必要があるアドレスを示します。シンボル結合を監視するだけの監査ライブラリは、sym->st_value の値を返すため、制御は結合記号定義に渡されます。監査ライブラリは、異なる値を返すことによって、シンボル結合を意図的にリダイレクトできます。
la_sparcv9_pltenter() への sym_name パラメータは、処理中のシンボルの名前を含みます。これは、32 ビットインタフェースから sym->st_name フィールドで使用できます。
uint_t la_pltexit(Elf32_Sym * sym, uint_t ndx, uintptr_t * refcook, uintptr_t * defcook, uintptr_t retval); uint_t la_pltexit64(Elf64_Sym * sym, uint_t ndx, uintptr_t * refcook, uintptr_t * defcook, uintptr_t retval, const char * sym_name); |
この関数は、結合通知のタグが付けられた 2 つのオブジェクト間の手続きリンク記号項目 (la_objopen() と la_symbind32()を参照) が返されて、制御が呼び出し側に到達するまでの間に呼び出されます。
sym、ndx、refcook、および defcookは、la_symbind32() に渡されるものと同じ情報を提供します。retval は、結合関数からの戻りコードです。la_pltexit64() への sym_name パラメータは、処理中のシンボルの名前を含み、32 ビット実装状態の sym->st_name フィールドから使用できます。
シンボル結合を監視する監査ライブラリは、retval を返します。監査ライブラリは意図的に異なる値を返すことができます。
このインタフェース関数は実験的なものです 「監査インタフェースの制限」 (「初期設定および終了ルーチン」を参照)。
uint_t la_objclose(uintptr_t * cookie); |
この関数はオブジェクトに対する終了コードが実行されてから、オブジェクトが読み込みを解除されるまでに呼び出されます (「初期設定および終了ルーチン」 を参照)。
cookie は、以前の la_objopen() から取得されていて、オブジェクトを特定します。戻り値は、ここではすべて無視されます。
次の単純な例では、動的実行可能プログラム date(1) によって読み込まれた各共有オブジェクトの依存関係の名前を出力する、監査ライブラリを作成しています。
$ cat audit.c #include <link.h> #include <stdio.h> uint_t la_version(uint_t version) { return (LAV_CURRENT); } uint_t la_objopen(Link_map * lmp, Lmid_t lmid, uintptr_t * cookie) { if (lmid == LM_ID_BASE) (void) printf("file: %s loaded¥n", lmp->l_name); return (0); } $ cc -o audit.so.1 -G -K pic -z defs audit.c -lmapmalloc -lc $ LD_AUDIT=./audit.so.1 date file: date loaded file: /usr/lib/libc.so.1 loaded file: /usr/lib/libdl.so.1 loaded file: /usr/locale/en_US/en_US.so.1 loaded Fri Mar 8 10:03:55 PST 1997 |
/usr/demo/link_audit の SUNWosdem パッケージには、rtld-監査インタフェースを使用する多数のデモアプリケーションが用意されています。
sotruss(1) と whocalls(1) は、SUNWtoo パッケージにも組み込まれています。 perfcnt と symbindrep はサンプルプログラムであり、実際の環境での使用を目的としていません。
la_pltexit() および ld_pltexit64() 関数の使用にはいくつかの制限があります。これらの制限は、呼び出し側と呼び出し先の間で余分なスタックフレームを挿入して、la_pltexit() 戻り値を獲得する方法を提供するための必要から生じたものです。la_*_pltenter() ルーチンだけを呼び出す場合には、妨害となるスタックを整理してから宛先関数に制御を譲渡できるため、上記は問題になりません。
これらの制限が原因で、la_pltexit() および la_pltexit64() は、上記の制限を改善するために今後のリリースで変更される可能性がある、実験的インタフェースとみなされます。問題がある場合には、la_pltexit() を避けるようにしてください。
スタックを直接検査するか、またはその状態について仮定をたてる少数の関数があります。これらの関数の例としては、 setjmp(3C) ファミリ、vfork(2) 、および構造へのポインタではなく構造を返す関数があります。これらの関数は、la_pltexit() をサポートするために作成される余分なスタックによって調整されます。
実行時リンカーは、このタイプの関数を検出できないため、監査ライブラリの作成元が、このようなルーチンの la_pltexit() を無効にする必要があります。