第 3 章「実行時リンカー」で説明したように、実行時リンカーは、メモリへのオブジェクトの割り当てやシンボルの結合を含む多数の操作を実行します。デバッグプログラムは、通常、これらの実行時リンカーの操作をアプリケーション解析の一部として記述する情報にアクセスする必要があります。これらのデバッグプログラムは、解析対象のアプリケーションに対する独立したプロセスとして実行されます。
このセクションでは、他のプロセスから動的にリンクされたアプリケーションを監視、変更するためにサポートされているインタフェースを説明します。このインタフェースは、rtld-デバッガインタフェースと呼ばれます。このインタフェースのアーキテクチャは、libthread_db(3T) で使用されるモデルに従っています。
rtld-デバッガインタフェースを使用する場合は、少なくとも次の 2 つのプロセスが関与します。
1 つまたは複数のターゲットプロセス。ターゲットプロセスは動的にリンクし、実行時リンカーとして /usr/lib/ld.so.1 を使用する必要がある。または、64 ビット SPARCV9 プロセスの場合は、/usr/lib/sparcv9/ld.so.1 を使用する必要がある
制御プロセスは、rtld-デバッガのインタフェースライブラリとリンクし、それを使用してターゲットプロセスの動的側面を検査する。64 ビット制御プロセスは、64 ビットおよび32 ビットtargets, howeverの両方のターゲットをデバッグできる。ただし、32 ビット制御プロセスは32 ビットターゲットに制限される
rtld-デバッガは、制御プロセスがデバッガであり、そのターゲットが動的実行可能なプログラムの場合に、最もよく使用されます。
rtld-デバッガインタフェースは、ターゲットプロセスに対して次のものを有効にします。
実行時リンカーとの最初の認識
動的オブジェクトの読み込みと読み込み解除の通知
読み込まれたオブジェクトすべてに関する情報の検索
手続きリンクテーブルエントリのステップオーバー
オブジェクトパッドの有効化
ターゲットプロセスを検査して操作できるようにするために、rtld-デバッガインタフェースは、エクスポートされたインタフェース、インポートされたインタフェース、およびエージェントを使用して、これらのインタフェース間で通信を行います。
制御プロセスは、librtld_db.so.1 によって提供される rtld-デバッガインタフェースにリンクされて、このライブラリからエクスポートされたインタフェースを要求します。このインタフェースは、/usr/include/rtld_db.h に定義されています。次に、librtld_db.so.1 は制御プロセスからインポートされたインタフェースを要求します。この対話によって、rtld-デバッガ インタフェースは、次のことを行うことができます。
ターゲットプロセス内のシンボルの検索
ターゲットプロセスのメモリの読み取りと書き込み
インポートされたインタフェースは多数の proc_service ルーチンから構成されています (「デバッガインポートインタフェース」を参照) 。ほとんどのデバッガは、このルーチンをすでに使用してプロセスを解析しています。
rtld-デバッガインタフェースは、rtld-デバッガインタフェースの要求により解析中のプロセスが停止することを前提としています。停止しない場合は、ターゲットプロセスの実行時リンカー内にあるデータ構造が、検査時に一貫した状態にない可能性があります。
次の図は、librtld_db.so.1、制御プロセス (デバッガ)、およびターゲットプロセス (動的実行可能プログラム) 間の情報の流れを示しています。
rtld-デバッガインタフェースは、実験的と見なされる proc_service インタフェース (/usr/include/proc_service.h) に依存します。rtld-デバッガインタフェースは、展開時に、proc_service インタフェース内の変更を追跡しなければならないことがあります。
rtld-デバッガインタフェースを使用する制御プロセスのサンプル実装状態は、/usr/demo/librtld_db の SUNWosdem パッケージに用意されています。 このデバッガ rdb は、proc_service インポートインタフェースの使用例を提示して、すべての librtld_db.so.1 インポートインタフェースの使用例を提示して、すべての rtld-デバッガインタフェースについて説明します。さらに詳しい情報は、サンプルデバッガをテストして得ることができます。
エージェントは、内部インタフェース構造を記述できる不透明なハンドルを提供して、エクスポートインタフェースとインポートインタフェースの間の通信機構となるものです。rtld-デバッガインタフェースは、いくつかのプロセスを同時に操作できるデバッガによる使用を目的としているため、これらのエージェントは、プロセスを特定するために使用されます。
struct ps_prochandle; |
制御プロセスによって、エクスポートインタフェースとインポートインタフェースの間で渡されるターゲットプロセスを特定するために作成される不透明な構造。
struct rd_agent; |
rtld-デバッガインタフェースによって、エクスポートインタフェースとインポートインタフェースの間で渡されるターゲットプロセスを特定するために作成される不透明な構造。
このセクションでは、/usr/lib/librtld_db.so.1 監査ライブラリによってエクスポートされる各インタフェースについて説明します。この節は、機能グループごとに分けられています。
rd_err_e rd_init(int version); |
この関数は、rtld-デバッガバージョン条件を確立します。現在バージョンは、RD_VERSION によって定義されます。.
制御プロセスのバージョン条件が使用可能な rtld-デバッガインタフェースよりも大きい場合は、RD_NOCAPAB が返されます。
rd_agent_t * rd_new(struct ps_prochandle * php); |
この関数は、新しいエクスポートのインタフェースエージェントを作成します。「php」は、制御プロセスによってターゲットプロセスを特定するために作成された cookie です。この cookie は、制御プロセスによってコンテキストを維持するために提供される重要なインタフェースで使用されるものであり、rtld-デバッガインタフェースに対して不透明です。
rd_err_e rd_reset(struct rd_agent * rdap); |
この関数は、rd_new() に指定された同じ ps_prochandle 構造に基づくエージェント内の情報をリセットします。この関数は、ターゲットプロセスが再スタートされると呼び出されます。
void rd_delete(struct rd_agent * rdap); |
次のエラー状態は、rtld-デバッガインタフェース (rtld_db.h に定義) によって返されます。
typedef enum { RD_ERR, RD_OK, RD_NOCAPAB, RD_DBERR, RD_NOBASE, RD_NODYNAM, RD_NOMAPS } rd_err_e; |
次のインタフェースは、エラー情報を収集するために使用できます。
char * rd_errstr(rd_err_e rderr); |
void rd_log(const int onoff); |
この関数は、ログ記録をオン (1) またはオフ (0) にします。ログ記録がオンの場合、制御プロセスによって提供されるインポートインタフェース関数 ps_plog() は、さらに詳しい診断情報によって呼び出されます。
実行時リンカーのリンクマップで維持される各オブジェクト情報の取得は (「名前空間の確立」 を参照)、rtld_db.h に定義された次の構造を使用して実現されます。
typedef struct rd_loadobj { psaddr_t rl_nameaddr; unsigned rl_flags; psaddr_t rl_base; psaddr_t rl_data_base; unsigned rl_lmident; psaddr_t rl_refnameaddr; psaddr_t rl_plt_base; unsigned rl_plt_size; psaddr_t rl_bend; psaddr_t rl_padstart; psaddr_t rl_padend; } rd_loadobj_t; |
文字列ポインタを含めて、この構造で指定されるアドレスはすべてターゲットプロセス内のアドレスであり、制御プロセス自体のアドレス空間のアドレスでないことに注意してください。
動的オブジェクトの名前を含む文字列へのポインタ
今後の使用のために予約
動的オブジェクトの基本アドレス
動的オブジェクトのデータセグメントの基本アドレス
リンクマップ識別子「名前空間の確立」を参照)
動的オブジェクトがフィルタの場合は (「フィルタとしての共有オブジェクト」を参照)、フィルタ対象の名前を指定する
これらの要素は、下方互換性のために存在するものであり、現在は使用されていない
オブジェクトのエンドアドレス (text + data + bss).
動的オブジェクト前のパッドの基本アドレス (「動的オブジェクトのパッド」参照)
動的オブジェクト後のパッドの基本アドレス (「動的オブジェクトのパッド」を参照)
次のルーチンは、このオブジェクトデータ構造を使用して実行時リンカーリンクマップリストの情報にアクセスしています。
typedef int rl_iter_f(const rd_loadobj_t *, void *); rd_err_e rd_loadobj_iter(rd_agent_t * rap, rl_iter_f * cb, void * clnt_data); |
この関数は、ターゲットプロセスに現在読み込まれている動的オブジェクトすべてを反復します。各反復時に、cb によって指定されたインポート関数が呼び出されます。clnt_data は、cb 呼び出しにデータを渡すために使用できます。各オブジェクトに関する情報は、スタックが割り当てられた volatile rd_loadobj_t 構造へのポインタを介して返されます。
cb ルーチンからの戻りコードは、rd_loadobj_iter() によってテストされ、次の意味を持ちます。
1 - リンクマップの処理を継続
0 - リンクマップの処理を停止して、制御プロセスに制御を返す
rd_loadobj_iter() は、正常だと RD_OK を返します。RD_NOMAPS が返される場合、実行時リンカーは、まだ初期リンクマップを読み込みません。
実行時リンカーの適用範囲内で発生し、制御プロセスが追跡できる特定のイベントがあります。これらのイベントは次のとおりです。
実行時リンカーは、すべての動的オブジェクトを読み込んで再配置し、読み込まれた各オブジェクトの .init セクションの呼び出しを開始 (「初期設定および終了ルーチン」 を参照)
実行時リンカーは、すべての .init セクションの呼び出しを終了して、基本実行可能プログラムに制御を渡す。
実行時リンカーは、動的オブジェクトを読み込みまたは読み込み解除のために呼び出される(「追加オブジェクトの読み込み」 を参照)。
これらのイベントは、次のインタフェース (sys/link.h と rtld_db.h に定義) を使用して監視できます。
typedef enum { RD_NONE = 0, RD_PREINIT, RD_POSTINIT, RD_DLACTIVITY } rd_event_e; /* * ways that the event notification can take place: */ typedef enum { RD_NOTIFY_BPT, RD_NOTIFY_AUTOBPT, RD_NOTIFY_SYSCALL } rd_notify_e; /* * information on ways that the event notification can take place: */ typedef struct rd_notify { rd_notify_e type; union { psaddr_t bptaddr; long syscallno; } u; } rd_notify_t; |
rderr_e rd_event_enable(struct rd_agent * rdap, int onoff); |
パフォーマンス上の理由から、現在、実行時リンカーはイベントの無効化を無視します。制御プロセスは、このルーチンへの最後の呼び出しが原因で指定のブレークポイントに到達しないと、想定することはできません。
rderr_e rd_event_addr(rd_agent_t * rdap, rd_event_e event, rd_notify_t * notify); |
この関数は、制御プログラムへの指定イベントの通知方法を指定します。
イベントタイプに従って、制御プロセスの通知は、「notify->u.syscallno」で特定されるチープなシステム呼び出しを呼び出すか、または「notify->u.bptaddr」によって指定されたアドレスでブレークポイントを実行することで行われます。システム呼び出しの追跡または実際のブレークポイントの設定は、制御プロセスが行う必要があります。
イベントが発生した場合は、rtld_db.h に定義された次のインタフェース によって追加情報を取得できます。
typedef enum { RD_NOSTATE = 0, RD_CONSISTENT, RD_ADD, RD_DELETE } rd_state_e; typedef struct rd_event_msg { rd_event_e type; union { rd_state_e state; } u; } rd_event_msg_t; |
rd_state_e 値の意味は次のとおりです。
使用可能な追加状態情報なし
リンクマップは安定した状態にあって、テスト可能
動的オブジェクトは削除処理中であり、リンクマップは安定した状態にない。リンクマップは、RD_CONSISTANT 状態に達するまでテストできない
動的オブジェクトは削除処理中であり、リンクマップは安定した状態にない。リンクマップは、RD_CONSISTANT 状態に達するまでテストできない
rderr_e rd_event_getmsg(struct rd_agent * rdap, rd_event_msg_t * msg); |
次の表は、異なる各イベントタイプで可能な状態を示しています。
RD_PREINIT |
RD_POSTINIT |
RD_DLACTIVITY |
---|---|---|
RD_NOSTATE |
RD_NOSTATE |
RD_CONSISTANT |
|
|
RD_ADD |
|
|
RD_DELETE |
rtld-デバッガインタフェースは、手続きリンクのテーブルエントリをスキップオーバーするための機能を提供します (「手続きリンクテーブル (プロセッサに固有)」を参照)。デバッガなどの制御プロセスは、初めて関数に介入するよう要求される場合、実際の手続きリンクテーブル処理をスキップしようとします。この結果、制御は、関数定義を検索するために実行時リンカーに渡されます。
次のインタフェースを使用すると、制御プロセスで実行時リンカーの手続きリンクテーブル処理にステップオーバーできます。制御プロセスは、ELF ファイルで提供される外部情報に基づいて、手続きリンクのテーブルエントリに遭遇する時期を判定できるものと想定されます。
ターゲットプロセスは、手続きリンクのテーブルエントリに介入すると、次のインタフェースを呼び出します。
rd_err_e rd_plt_resolution(rd_agent_t * rdap, paddr_t pc, lwpid_t lwpid, paddr_t plt_base, rd_plt_info_t * rpi); |
この関数は、現在の手続きリンクテーブルエントリの解決状態と、それをスキップする方法に関する情報を返します。
pc は、手続きリンクテーブルエントリの最初の命令を表わします。lwpid は lwp 識別子を提供し、plt_base は手続きリンクテーブルの基本アドレスを提供します。これらの 3 つの変数は、各種のアーキテクチャが手続きリンクテーブルを処理するため十分な情報を提供します。
rpi は、次のデータ構造 (rtld_db.h に定義) に定義された、手続きリンクのテーブルエントリに関する詳しい情報を提供します。
typedef enum { RD_RESOLVE_NONE, RD_RESOLVE_STEP, RD_RESOLVE_TARGET, RD_RESOLVE_TARGET_STEP } rd_skip_e; typedef struct rd_plt_info { rd_skip_e pi_skip_method; long pi_nstep; psaddr_t pi_target; } rd_plt_info_t; |
次のシナリオは rd_plt_info_t 戻り値から考えられます。
これが、この手続きリンクテーブル全体の最初の呼び出しであるため、実行時リンカーによって解決する必要がある。rd_plt_info_t には、次のものが含まれる
{RD_RESOLVE_TARGET_STEP, M, <BREAK>} |
制御プロセスは、BREAK にブレークポイントを設定し、ターゲットプロセスを続けます。ブレークポイントに達すると、手続きリンクのテーブルエントリ処理は終了し、制御プロセスは M 命令を宛先関数にステップできます。
これは、この手続きリンクテーブル全体で N 番め。rd_plt_info_t には、次のものが含まれる
{RD_RESOLVE_STEP, M, 0} |
手続きリンクのテーブルエントリはすでに解決されていて、制御プロセスは M 命令を宛先関数にステップできます。
今後の実装状態では、ターゲット関数にブレークポイントを直接設定する方法として、RD_RESOLVE_TARGET を使用する可能性がありますが、この機能は、今回のバージョンの rtld-デバッガインタフェースではまだ使用できません。
実行時リンカーのデフォルト動作は、オペレーティングシステムに依存して、最も効率的に参照できる場所に動的オブジェクトを読み込みます。制御プロセスの中には、ターゲットプロセスのメモリに読み込まれたオブジェクトの回りにパッドがあることによって、利益を受けるものがあります。このインタフェースを使用すると、制御プロセスは、このパッドを要求できます。
rd_err_e rd_objpad_enable(struct rd_agent * rdap, size_t padsize); |
この関数は、ターゲットプロセスによって続けて読み込まれたオブジェクトのパッドを有効、または無効にします。パッドは読み込まれたオブジェクトの両側で行われます。
padsize は、メモリに読み込まれたオブジェクトの前後両方で維持されるパッドのサイズをバイト数で指定します。このパッドは、mmap(2) に PROT_NONE 権と MAP_NORESERVE フラグをつけて使用して、メモリ割り当てとして予約できます。実行時リンカーは、割り当てられたオブジェクトに隣接するターゲットプロセスの仮想アドレス空間の領域を効果的に予約します。これらの領域は、制御プロセスによって後に利用できます。
padsize を 0 にすると、後のオブジェクトに対するオブジェクトパッドは無効になります。
mmap(2) を /dev/zero から、MAP_NORESERVE によって使用して取得される予約は、proc(1) 機能を使用して、rd_loadobj_t に提供されたリンクマップ情報を参照することによって報告できます。
制御プロセスが librtld_db.so.1 に対して提供しなければならないインポートインタフェースは、/usr/include/proc_service.h に定義されています。これらの proc_service 関数のサンプル実装状態は、rdb デモデバッガにあります。rtld-デバッガインタフェースは、使用可能な proc_service インタフェースのサブセットだけを使用します。rtld-デバッガインタフェースの今後のバージョンでは、互換性のない変更を作成することなく、追加 proc_service インタフェースを利用できる可能性があります。
次のインタフェースは、現在、rtld-デバッガインタフェースによって使用されています。
ps_err_e ps_pauxv(const struct ps_prochandle * ph, auxv_t ** aux); |
この関数は、auxv ベクトルのコピーへのポインタを返します。auxv ベクトル情報は、割り当てられた構造にコピーされるため、このポインタの存続期間は、prochandle が有効な間になります。
ps_err_e ps_pread(const struct ps_prochandle * ph, paddr_t addr, char * buf, int size); |
ps_err_e ps_pwrite(const struct ps_prochandle * ph, paddr_t addr, char * buf, int size); |
void ps_plog(const char * fmt, ...); |
この関数は、rtld-デバッガインタフェースから追加診断情報によって呼び出されます。この診断情報をどこに記録するか、または記録するかどうかは、制御プロセスが決める必要があります。ps_plog() の引数は、printf(3S) 形式に従っています。
ps_err_e ps_pglobal_lookup(const struct ps_prochandle * ph, const char * obj, const char * name, ulong_t * sym_addr); |
この関数は、ターゲットプロセス ph 内のオブジェクト obj 内の記号 name を検索します。記号が検出されると、記号のアドレスが sym_addr に保存されます。
ps_err_e ps_pglobal_sym(const struct ps_prochandle * ph, const char * obj, const char * name, ps_sym_t * sym); |
この関数は、ターゲットプロセス ph 内のオブジェクト obj 内の記号 name を検索します。記号が検出されると、記述子 sym は埋められます。
rtld-デバッガインタフェースがアプリケーションまたは実行時リンカー内の記号を検出してから、リンクマップを作成する必要があるイベントでは、obj に対する次の予約値を使用できます。
#define PS_OBJ_EXEC ((const char *)0x0) /* application id */ #define PS_OBJ_LDSO ((const char *)0x1) /* runtime linker id */ |
制御プロセスがこれらのオブジェクトの記号テーブルを検出するために使用できる機構の 1 つに、次の擬似コードを使用する procfs ファイルシステムを介するものがあります。
ioctl(.., PIOCNAUXV, ...) - obtain AUX vectors ldsoaddr = auxv[AT_BASE]; ldsofd = ioctl(..., PIOCOPENM, &ldsoaddr); /* process elf information found in ldsofd ... */ execfd = ioctl(.., PIOCOPENM, 0); /* process elf information found in execfd ... */ |
ファイル記述子が見つかったら、ELF ファイルは、制御プログラムによってその記号情報をテストできます。