リンカーとライブラリ

実行時リンカーのデバッガインタフェース

実行時リンカーは、メモリーへのオブジェクトの割り当てやシンボルの結合を含む多数の操作を実行します。デバッグプログラムは、通常、これらの実行時リンカーの操作をアプリケーション解析の一部として記述する情報にアクセスする必要があります。これらのデバッグプログラムは、解析対象のアプリケーションに対する独立したプロセスとして実行されます。

このセクションでは、他のプロセスから動的にリンクされたアプリケーションを監視、変更する rtld-デバッガインタフェースについて説明します。このインタフェースのアーキテクチャは、libthread_db(3THR) で使用されるモデルに従っています。

「rtld-デバッガ」インタフェースを使用する場合は、少なくとも次の 2 つのプロセスが関与します。

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、制御プロセス (デバッガ)、およびターゲットプロセス (動的実行可能プログラム) 間の情報の流れを、次の図に示します。

図 6–1 rtld-デバッガの情報の流れ

rtld-デバッガの情報の流れ。


注 –

「rtld-デバッガ」インタフェースは、実験的と見なされる proc_service インタフェース (/usr/include/proc_service.h) に依存します。「rtld-デバッガ」インタフェースは、展開時に、proc_service インタフェース内の変更を追跡しなければならないことがあります。


「rtld-デバッガ」インタフェースを使用する制御プロセスのサンプル実装状態は、/usr/demo/librtld_dbSUNWosdem パッケージに用意されています。 このデバッガ rdb は、proc_service インポートインタフェースの使用例、およびすべての librtld_db.so.1 エクスポートインタフェースの必須呼び出しシーケンスを示します。次のセクションでは、「rtld-デバッガ」インタフェースについて説明します。さらに詳しい情報は、サンプルデバッガをテストして入手することができます。

デバッガインタフェースのエージェント

エージェントは、内部インタフェース構造を記述可能な不透明なハンドルを提供します。エージェントは、エクスポートインタフェースとインポートインタフェースとの間の通信機構も提供します。「rtld-デバッガ」インタフェースは、いくつかのプロセスを同時に操作できるデバッガによる使用を目的としているため、これらのエージェントは、プロセスを特定するために使用されます。

struct ps_prochandle

制御プロセスによって、エクスポートインタフェースとインポートインタフェースの間で渡されるターゲットプロセスを特定するために作成される不透明な構造です。

struct rd_agent

「rtld-デバッガ」インタフェースによって、エクスポートインタフェースとインポートインタフェースの間で渡されるターゲットプロセスを特定するために作成される不透明な構造です。

デバッガエクスポートインタフェース

このセクションでは、/usr/lib/librtld_db.so.1 監査ライブラリによってエクスポートされるさまざまなインタフェースについて説明します。機能グループごとに分けて説明します。

エージェント操作インタフェース

rd_init()

この関数は、「rtld-デバッガ」バージョン条件を確立します。ベースとなるバージョンは、RD_VERSION1 として定義されています。現在の「バージョン」は常に RD_VERSION で定義されます。

rd_err_e rd_init(int version);

Solaris 8 10/00 で追加されたバージョン RD_VERSION2 は、rd_loadobj_t 構造体を拡張するものです。詳細は、読み込み可能オブジェクトの走査rl_flags rl_bend、および rl_dynamic フィールドを参照してください。

Solaris 8 01/01 で追加されたバージョン RD_VERSION3 は、rd_plt_info_t 構造体を拡張するものです。詳細は、プロシージャのリンクテーブルのスキップpi_baddr および pi_flags フィールドを参照してください。

制御プロセスのバージョン条件が使用可能な「rtld-デバッガ」インタフェースよりも大きい場合は、RD_NOCAPAB が返されます。

rd_new()

この関数は、新しいエクスポートのインタフェースエージェントを作成します。

rd_agent_t * rd_new(struct ps_prochandle * php);

php は、制御プロセスによってターゲットプロセスを特定するために作成された cookie です。この cookie は、制御プロセスによってコンテキストを維持するために提供される重要なインタフェースで使用されるものであり、「rtld-デバッガ」インタフェースに対して不透明です。

rd_reset()

この関数は、rd_new() に指定された同じ ps_prochandle 構造に基づくエージェント内の情報をリセットします。

rd_err_e rd_reset(struct rd_agent * rdap);

この関数は、ターゲットプロセスが再スタートされると呼び出されます。

rd_delete()

この関数は、エージェントを削除し、それに関連するすべての状態を解除します。

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;

次のインタフェースは、エラー情報を収集するために使用できます。

rd_errstr()

この関数は、エラーコード「rderr」を記述する記述エラー文字列を返します。

char * rd_errstr(rd_err_e rderr);
rd_log()

この関数は、ログ記録をオン (1) またはオフ (0) にします。


void rd_log(const int onoff);

ログ記録がオンの場合、制御プロセスによって提供されるインポートインタフェース関数 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;
        psaddt_t        rl_dynamic;
} rd_loadobj_t;

文字列ポインタを含めて、この構造で指定されるアドレスはすべてターゲットプロセス内のアドレスであり、制御プロセス自体のアドレス空間のアドレスでないことに注意してください。

rl_nameaddr

動的オブジェクトの名前を含む文字列へのポインタ

rl_flags

リビジョン RD_VERSION2 では、動的に読み込まれる再配置可能オブジェクトは RD_FLG_MEM_OBJECT で識別されます。

rl_base

動的オブジェクトの基本アドレス

rl_data_base

動的オブジェクトのデータセグメントの基本アドレス

rl_lmident

リンクマップ識別子 (名前空間の確立を参照)

rl_refnameaddr

動的オブジェクトがフィルタの場合は、フィルティーの名前を指定する

rl_plt_baserl_plt_size

これらの要素は、下方互換性のために存在するものであり、現在は使用されていない

rl_bend

オブジェクトのエンドアドレス (text + data + bss)。リビジョン RD_VERSION2 では、動的に読み込まれる再配置可能オブジェクトの場合、この要素は作成されたオブジェクトの最後を指します。このオブジェクトには、自身のセクションヘッダーが含まれています。

rl_padstart

動的オブジェクト前のパッドの基本アドレス (動的オブジェクトのパッドを参照)

rl_padend

動的オブジェクト後のパッドの基本アドレス (動的オブジェクトのパッドを参照)

rl_dynamic

このフィールドは RD_VERSION2 に追加されたもので、DT_CHECKSUM (表 7–43 を参照) のエントリへの参照を可能にするオブジェクトの動的セクションのベースアドレスを提供します。

rd_loadobj_iter() ルーチンは、このオブジェクトデータ構造を使用して実行時リンカーのリンクマップリストの情報にアクセスします。

rd_loadobj_iter()

この関数は、ターゲットプロセスに現在読み込まれている動的オブジェクトすべてを反復します。

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 が返される場合、実行時リンカーは、まだ初期リンクマップを読み込みません。

イベント通知

制御プロセスは、実行時リンカーの適用範囲内で発生する特定のイベントを追跡できます。これらのイベントは次のとおりです。

RD_PREINIT

実行時リンカーは、すべての動的オブジェクトを読み込んで再配置し、読み込まれた各オブジェクトの .init セクションの呼び出しを開始する。

RD_POSTINIT

実行時リンカーは、すべての .init セクションの呼び出しを終了して、基本実行可能プログラムに制御を渡す。

RD_DLACTIVITY

実行時リンカーは、動的オブジェクトを読み込みまたは読み込み解除のために呼び出される。

これらのイベントは、次のインタフェース (sys/link.hrtld_db.h に定義) を使用して監視できます。

typedef enum {
        RD_NONE = 0,
        RD_PREINIT,
        RD_POSTINIT,
        RD_DLACTIVITY
} rd_event_e;
 
/*
 * イベント通知の方法
 */
typedef enum {
        RD_NOTIFY_BPT,
        RD_NOTIFY_AUTOBPT,
        RD_NOTIFY_SYSCALL
} rd_notify_e;
 
/*
 * イベント通知の方法についての情報
 */
typedef struct rd_notify {
        rd_notify_e     type;
        union {
                psaddr_t        bptaddr;
                long            syscallno;
        } u;
} rd_notify_t;

イベントを追跡する関数を次に示します。

rd_event_enable()

この関数は、イベント監視を有効 (1) または無効 (0) にします。

rd_err_e rd_event_enable(struct rd_agent * rdap, int onoff);

注 –

パフォーマンス上の理由から、現在、実行時リンカーはイベントの無効化を無視します。制御プロセスは、このルーチンへの最後の呼び出しが原因で指定のブレークポイントに到達しないと、想定することはできません。


rd_event_addr()

この関数は、制御プログラムへの指定イベントの通知方法を指定します。

rd_err_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_NOSTATE

使用可能な追加状態情報なし

RD_CONSISTANT

リンクマップは安定した状態にあってテスト可能

RD_ADD

動的オブジェクトは削除処理中であり、リンクマップは安定した状態にない。リンクマップは、RD_CONSISTANT 状態に達するまでテストできない

RD_DELETE

動的オブジェクトは削除処理中であり、リンクマップは安定した状態にない。リンクマップは、RD_CONSISTANT 状態に達するまでテストできない

rd_event_getmsg() 関数を使用して、このイベント状態情報を取得します。

rd_event_getmsg()

この関数は、イベントに関する追加情報を提供します。

rd_err_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_plt_resolution() インタフェースを呼び出します。

rd_plt_resolution()

この関数は、現在のプロシージャのリンクテーブルエントリの解決状態と、それをスキップする方法に関する情報を返します。


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 は、プロシージャのリンクテーブルエントリの最初の命令を表します。lwpidlwp 識別子を提供し、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;
        psaddr_t        pi_baddr;
        unsigned int    pi_flags;
} rd_plt_info_t;

#define RD_FLG_PI_PLTBOUND     0x0001

rd_plt_info_t 構造体の要素を次に示します。

pi_skip_method

プロシージャのリンクテーブルエントリがどのように扱われるかを示す。rd_skip_e 値内の 1 つに設定される

pi_nstep

RD_RESOLVE_STEP または RD_RESOLVE_TARGET_STEP が返された時にステップオーバーする命令がいくつあるかを示す

pi_target

RD_RESOLVE_TARGET_STEP または RD_RESOLVE_TARGET が返された時にブレークポイントを設定するアドレス指定する

pi_baddr

RD_VERSION3 で追加された、プロシージャのリンクテーブルの宛先アドレス。pi_flags フィールドの RD_FLG_PI_PLTBOUND フラグが設定されると、この要素は解決された (結合された) 宛先アドレスを示す

pi_flags

RD_VERSION3 で追加されたフラグフィールド。フラグ RD_FLG_PI_PLTBOUND は、pi_baddr フィールドで取得できる宛先アドレスへ解決された (結合された) プロシージャのリンクエントリを示す

次のシナリオは rd_plt_info_t 戻り値から考えられます。

動的オブジェクトのパッド

実行時リンカーのデフォルト動作は、オペレーティングシステムに依存して、最も効率的に参照できる場所に動的オブジェクトを読み込みます。制御プロセスの中には、ターゲットプロセスのメモリーに読み込まれたオブジェクトの周りにパッドがあることによって、利益を受けるものがあります。このインタフェースを使用すると、制御プロセスは、このパッドを要求できます。

rd_objpad_enable()

この関数は、ターゲットプロセスによって続けて読み込まれたオブジェクトのパッドを有効または無効にします。パッドは読み込まれたオブジェクトの両側で行われます。

rd_err_e rd_objpad_enable(struct rd_agent * rdap, size_t padsize);

padsize は、メモリーに読み込まれたオブジェクトの前後両方で維持されるパッドのサイズをバイト数で指定します。このパッドは、mmap(2) に対し PROT_NONE アクセス権と MAP_NORESERVE フラグを指定して、メモリー割り当てとして予約されます。実行時リンカーは、読み込まれたオブジェクトに隣接するターゲットプロセスの仮想アドレス空間の領域を効果的に予約します。これらの領域は、制御プロセスによって後に利用できます。

padsize を 0 にすると、後のオブジェクトに対するオブジェクトパッドは無効になります。


注 –

mmap(2)MAP_NORESERVE を指定して /dev/zero から取得したメモリー割り当ての予約は、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_pauxv()

この関数は、auxv ベクトルのコピーへのポインタを返します。

ps_err_e ps_pauxv(const struct ps_prochandle * ph, auxv_t ** aux);

auxv ベクトル情報は、割り当てられた構造にコピーされるため、このポインタの存続期間は、ps_prochandle が有効な間になります。

ps_pread()

この関数は、ターゲットプロセスからデータを読み取ります。


ps_err_e ps_pread(const struct ps_prochandle * ph, paddr_t addr,
        char * buf, int size);

ターゲットプロセス内のアドレス addr から、size バイトが buf にコピーされます。

ps_pwrite()

この関数は、ターゲットプロセスにデータを書き込みます。

ps_err_e ps_pwrite(const struct ps_prochandle * ph, paddr_t addr,
        char * buf, int size);

buf から size バイトが、ターゲットプロセスのアドレス addr にコピーされます。

ps_plog()

この関数は、「rtld-デバッガ」インタフェースから追加診断情報によって呼び出されます。

void ps_plog(const char * fmt, ...);

この診断情報をどこに記録するか、または記録するかどうかは、制御プロセスが決める必要があります。ps_plog() の引数は、printf(3C) 形式に従います。

ps_pglobal_lookup()

この関数は、ターゲットプロセス内のシンボルを検索します。

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_pglobal_sym()

この関数は、ターゲットプロセス内のシンボルを検索します。

ps_err_e ps_pglobal_sym(const struct ps_prochandle * ph,
        const char * obj, const char * name, ps_sym_t * sym_desc);

ターゲットプロセス ph 内のオブジェクト obj 内で、シンボル name が検索されます。シンボルが検出されると、シンボルの記述子が sym_desc に保存されます。

「rtld-デバッガ」インタフェースがアプリケーションまたは実行時リンカー内の記号を検出してから、リンクマップを作成する必要があるイベントでは、obj に対する次の予約値を使用できます。

#define PS_OBJ_EXEC ((const char *)0x0)  /* アプリケーション id */
#define PS_OBJ_LDSO ((const char *)0x1)  /* 実行時リンカー id */

制御プロセスは、次の擬似コードを使用して、これらのオブジェクト用の procfs ファイルシステムを利用できます。

ioctl(.., PIOCNAUXV, ...)       - obtain AUX vectors
ldsoaddr = auxv[AT_BASE];
ldsofd = ioctl(..., PIOCOPENM, &ldsoaddr);
 
/* elf プロセス情報が ldsofd で検出される... */
 
execfd = ioctl(.., PIOCOPENM, 0);
 
/* elf プロセス情報が execfd で検出される... */

ファイル記述子が見つかったら、ELF ファイルは、制御プログラムによってその記号情報をテストできます。