dbx コマンドによるデバッグ

第 20 章 dbx と動的リンカー

dbx は動的にリンクされた共有ライブラリのデバッグを完全にサポートしています。ただし、これらのライブラリが -g オプションを使用してインストールされていることが前提になります。

この章は、次の各節から構成されています。

概要

動的リンカーは“rtld”、“実行時 ld”、または“ld.so”とも呼ばれ、実行中のアプリケーションに共有オブジェクト (ロードオブジェクト) を組み込むように準備します。rtld が稼働状態になるのは主に次の 2 つの場合です。

  1. プログラムの起動時

    プログラムの起動時、rtld はまずリンク時に指定されたすべての共有オブジェクトを動的に読み込みます (ldd (1) を使用すれば、プログラムによって読み込まれる共有オブジェクトを調べることができます)。これらは「あらかじめ読み込まれた」共有オブジェクトで、一般に libc.so、libC.so、libX.so などがあります。

  2. アプリケーションから呼び出しがあった場合

    アプリケーションでは、関数呼び出し dlopen(3) と dlclose(3) を使用して共有オブジェクトやプレーンな実行可能ファイルの読み込みや読み込みの取り消しを行います。共有オブジェクト (.so) や通常の実行可能ファイル (a.out) のことを、dbx では「ロードオブジェクト」といいます。

動的リンカーは、読み込まれたすべてのオブジェクトのリストを「リンクマップ」というリストに保持します。リンクマップはユーザメモリに管理され、スレッドデバッグ用の特別なシステムライブラリ libthread_db.so によって間接的にアクセスされます。

dbx はリンクマップから次のことを調べます。

これらのデータ構造が壊れていると、dbx は正しい動作をしないことがあります。

共有オブジェクトのデバッグ

dbx は、あらかじめ読み込まれた共有オブジェクトと dlopen() によって開かれた共有オブジェクトの両方をデバッグできます。デバッグ時のいくつかの制限事項については後述します。

起動手順

あらかじめ読み込まれた共有オブジェクトにブレークポイントを置くためには、ルーチンのアドレスを dbx に知らせる必要があり、dbx がルーチンのアドレスを知るためには、共有オブジェクトのベースアドレスを知らなければなりません。たとえば次のような作業を行うとします。


stop in printf
run

このような単純な作業でも、dbx には特別な配慮が必要です。新しいプログラムが読み込まれるたびに 、dbxrtld がリンクマップの作成を完了した場所までプログラムを自動的に実行します。dbx はリンクマップを読み取り、ベースアドレスを格納します。その後、プロセスは終了し、メッセージとプロンプトが表示されます。dbx は、これらの動作中にメッセージを表示しません。

この時点で、ベース読み込みアドレスとともに libc.so のシンボルテーブルを調べることができるため、printf のアドレスがわかります。

rtld によってリンクマップが作成されるのを待機し、リンクマップの先頭にアクセスするまでの dbx の動作を「rtld ハンドシェーク」と呼びます。rtld がリンクマップを作成し、dbx がすべてのシンボルテーブルを読み取ると、イベント syncrtld が発生します。

この方式では、dbx はプログラムの実行時、各共有ライブラリが同じベースアドレスに読み込まれていることを前提とします。ただし、プログラムを読み込んで実行するまでに LD_LIBRARY_PATH を変更した場合にかぎり、ライブラリは同じアドレスに読み込まれません。この変数が変更された場合、dbx は新しいアドレスのメッセージを出力しますが、変更後の共有オブジェクト内のブレークポイントが正しくない場合があります。

起動手順と .init セクション

.init セクションは、共有オブジェクトの読み込み時に実行される、その共有オブジェクトのコードの一部分です。たとえば、.init セクションは、C++ 実行時システムがすべての静的初期化関数を呼び出すときに使用します。

動的リンカーは最初にすべての共有オブジェクトにマップインし、それらのオブジェクトをリンクマップに登録します。その後、動的リンカーはリンクマップに含まれる各オブジェクトの .init セクションを順に実行していきます。

dlopen() と dlclose()

dbx は、dlopen または dlclose の発生を自動的に検出し、読み込んだオブジェクトのシンボルテーブルを読み込みます。読み込まれたオブジェクトへのブレークポイントの設定やデバッグは、プログラムのほかの部分と同じように行うことができます。

共有オブジェクトの読み込みが取り消されると、シンボルテーブルは破棄され、ブレークポイントは status を要求すると“defunct”(消滅) と報告されます。残念ながら、直後の run 操作でオブジェクトが読み込まれた場合に、ブレークポイントを自動的に有効にする方法はありません。

dlopendlclose の 2 つのイベントを when コマンドやシェルプログラムで使用することにより、dlopen 型の共有オブジェクトでのブレークポイント管理の負荷を軽減できます。

修正継続機能 (fix と continue)

共有オブジェクトに対して fixcontinue を正しく動作させるには、オープンの方法を変更する必要があります。モード 'RTLD_NOW|RTLD_GLOBAL' または 'RTLD_LAZY|RTLD_GLOBAL' を使用してください。

プロシージャ・リンケージ・テーブル (PLT)

PLT は、共有オブジェクトの境界間の呼び出しを容易にするために rtld によって使用される構造体です。たとえば、printf の呼び出しはこの間接テーブルによって行います。その方法の詳細については、SVR4 ABI に関する汎用リファレンスマニュアルおよびプロセッサ固有のリファレンスマニュアルを参照してください。

複数の PLT 間で step コマンドと next コマンドを操作するために、dbx は各ロードオブジェクトの PLT テーブルを追跡する必要があります。テーブル情報は rtld ハンドシェークと同時に入手されます。

動的にリンクされたライブラリにブレークポイントを設定する

実行時リンカーとの間でプログラムインタフェースを使用するコード、すなわち dlopen()dlclose()、およびそれらに関連する関数を呼び出すコードをデバッグするときは、デバッガの全機能を利用できます。実行時リンカーは、プログラムの実行中に共有ライブラリをリンクしたり、そのリンクを解除したりします。dlopen()dlclose() のサポートにより、動的にリンクされている共有ライブラリの関数もプログラムの起動時にリンクされたライブラリの関数と同様に、ステップ実行したり、ブレークポイントを設定したりできます。

3 つの例外

  1. dlopen されたライブラリが dlopen() によって読み込まれる前に、そのライブラリにブレークポイントを設定することはできません。

  2. dlopen されたフィルタライブラリ内の最初の関数が呼び出されるまで、フィルタライブラリにブレークポイントを設定することはできません。

  3. ライブラリが dlopen() によって読み込まれるとき、_init() という名前の初期化ルーチンが呼び出されます。このルーチンは、ライブラリ内のほかのルーチンを呼び出すことがあります。この初期化が終わるまで、dbx は読み込まれたライブラリにブレークポイントを置くことができません。具体的に言うと、dlopen によって読み込まれたライブラリの _init()内dbx を停止させることはできません。