Oracle Solaris Studio 12.2: dbx コマンドによるデバッグ

第 11 章 マルチスレッドアプリケーションのデバッグ

dbx では Solaris スレッドや POSIX スレッドを使用するマルチスレッドアプリケーションをデバッグできます。dbx には、各スレッドのスタックトレースの確認、全スレッドの再実行、特定のスレッドに対する stepnext の実行、スレッド間の移動をする機能があります。

dbx は、libthread.so が使用されているかどうかを検出することによって、マルチスレッドプログラムかどうかを認識します。プログラムは、-lthread または -mt を使用してコンパイルすることによって明示的に、あるいは -lpthread を使用してコンパイルすることによって暗黙的に libthread.so を使用します。

この章では dbx の thread コマンドを使用して、スレッドに関する情報を入手したり、デバッグを行う方法について説明します。

この章の内容は次のとおりです。

マルチスレッドデバッグについて

dbx は、マルチスレッドプログラムを検出すると、libthread_db.so の dlopen を試行します。これは、/usr/lib にあるスレッドデバッグ用の特別なシステムライブラリです。

dbx は同期的に動作します。つまり、スレッドか軽量プロセス (LWP) のいずれかが停止すると、ほかのスレッドおよび LWP もすべて自動的に停止します。この動作は、「世界停止 (stop the world)」モデルと呼ばれる場合があります。


注 –

マルチスレッドプログラミングと LWP については、『Solaris マルチスレッド のプログラミング』を参照してください。


スレッド情報

dbx では、次のスレッド情報を入手できます。


(dbx) threads
    t@1 a l@1  ?()  running   in main()
    t@2      ?() asleep on 0xef751450  in_swtch()
    t@3 b l@2  ?()  running in sigwait()
    t@4     consumer()  asleep on 0x22bb0 in _lwp_sema_wait()
  *>t@5 b l@4 consumer()  breakpoint     in Queue_dequeue()
    t@6 b l@5 producer()     running       in _thread_start()
(dbx)

ネイティブコードに対して、情報の各行の内容は次のとおりです。

Java コードでは、情報の各行は次で構成されています。

表 11–1 スレッドの状態と LWP の状態

スレッドの状態と LWP の状態  

内容の説明  

中断 

スレッドは明示的に中断されています。 

実行可能 

スレッドは実行可能であり、コンピューティング可能なリソースとして LWP を待機しています。 

ゾンビ 

切り離されたスレッドが存在する場合 (thr_exit())、次の関数を使用して再接続するまではそのスレッドはゾンビ状態にあります。thr_join()THR_DETACHED は、スレッドの生成時に指定されたフラグです (thr_create())。非結合のスレッドは、再実行されるまでゾンビ状態です。

syncobj でスリープ中

スレッドは所定の同期オブジェクトでブロックされています。libthreadlibthread_db によるサポートレベルにより、syncobj が伝える情報は単純な 16 進アドレスになったり、より詳細な内容になります。

アクティブ 

LWP でスレッドがアクティブですが、dbx は LWP をアクセスできません。

未知 

dbx では状態を判定できません。

lwpstate

結合スレッドやアクティブスレッドの状態に、LWP の状態が関連付けられています。 

実行中 

LWP が実行中でしたが、ほかの LWP と同期して停止しました。 

システムコール num

所定のシステムコール番号の入口で LWP が停止しました。 

システムコール num 戻り

所定のシステムコール番号の出口で LWP が停止しました。 

ジョブコントロール 

ジョブコントロールにより、LWP が停止しました。 

LWP 中断 

LWP がカーネルでブロックされています。 

シングル中断 

LWP により、1 ステップが終了しました。 

ブレークポイント 

LWP がブレークポイントに達しました。 

障害 num

LWP に所定の障害番号が発生しました。 

シグナル name

LWP に所定のシグナルが発生しました。 

プロセス sync 

この LWP が所属するプロセスの実行が開始しました。 

LWP 終了 

LWP は終了プロセス中です。 

別のスレッドのコンテキストの表示

表示コンテキストを別のスレッドに切り替えるには、thread コマンドを使用します。この構文は次のとおりです。


thread [-blocks] [-blockedby] [-info] [-hide] [-unhide] [-suspend] [-resume] thread_id

現在のスレッドを表示するには、次のように入力します。


thread

スレッド thread_id に切り替えるには、次のように入力します。


thread thread_id

thread コマンドの詳細については、thread コマンド」を参照してください。

スレッドリストの表示

スレッドリストを表示するには、threads コマンドを使用します。この構文は次のとおりです。


threads [-all] [-mode [all|filter] [auto|manual]]

既知のスレッドすべてのリストを表示するには、次のように入力します。


threads

通常は表示されないスレッド (ゾンビ) などを表示するには、次のように入力します。


threads -all

スレッドリストについては、「スレッド情報」を参照してください。

threads コマンドの詳細については、threads コマンド」を参照してください。

実行の再開

プログラムの実行を再開するには、cont コマンドを使用します。プログラム現在、スレッドは同期ブレークポイントを使用して、すべてのスレッドが実行を再開するようにしています。

ただし、シングルスレッドは、call コマンドに -resumeone オプションを付けて使用することにより再開できます (call コマンド」を参照)。

多数のスレッドが関数 lookup() を呼び出すマルチスレッドアプリケーションをデバッグする場合の 2 つのシナリオを次に示します。

strcmp() を呼び出すと、dbx は呼び出しの間、すべてのスレッドを再開することがありますが、これは dbx の動作 (next コマンドを使用して、シングルステップ実行をする場合) に似ています。この動作は、t@1 のみを再開すると、strcmp() が別のスレッドによって所有されているロックを奪取しようと試みた場合に、デッドロックが発生する可能性があるためです。

この場合にすべてのスレッドを再開することの欠点は、strcmp() の呼び出し中に lookup() のブレークポイントにヒットして、dbxt@2 などのほかのスレッドを処理できないことです。次のような警告が表示されます。

イベント無限ループにより次のハンドラ中でイベントの取りこぼしが起きます。

イベントの再入 第 1 イベント BPT(VID 6、TID 6、PC echo+0x8) 第 2 イベント BPT(VID 10、TID 10、PC echo+0x8) 以下のハンドラはイベントを処理しません。

そのような場合は、条件式で呼び出された関数が相互排他ロックを奪取しないことが確実であれば、-resumeone イベント修飾子を使用して、dbx t@1 のみを再開させることができます。


stop in lookup -resumeone -if strcmp(name, "troublesome") == 0

strcmp() を評価するために、lookup() のブレークポイントをヒットしたスレッドのみが再開されます。

この方法は、次のような状況では無効です。

スレッド作成動作について

次の例に示すように、アプリケーションが thr_create イベントおよび thr_exit イベントを使用して、どれくらい頻繁にスレッドを作成および終了しているかを知ることができます。


(dbx) trace thr_create
(dbx) trace thr_exit
(dbx) run

trace: thread created t@2 on l@2
trace: thread created t@3 on l@3
trace: thread created t@4 on l@4
trace: thr_exit t@4
trace: thr_exit t@3
trace: thr_exit t@2

ここでは、アプリケーションが 3 つのスレッドを作成します。スレッドは作成されたのとは逆の順序で終了し、アプリケーションにそれ以上のスレッドがある場合は、スレッドが累積されてリソースを消費します。

有用な情報を得るため、別のセッションで次のコマンドを実行してみてください。


(dbx) when thr_create { echo "XXX thread $newthread created by $thread"; }
XXX thread t@2 created by t@1
XXX thread t@3 created by t@1
XXX thread t@4 created by t@1

出力を見ると、3 つのスレッドすべてがスレッド t@1 によって作成されていることがわかります。これは、一般的なマルチスレッド化のパターンです。

スレッド t@3 を、その出力セットからデバッグする場合を考えます。次のようにすると、スレッド t@3 が作成されたポイントでアプリケーションを停止できます。


(dbx) stop thr_create t@3
(dbx) run
t@1 (l@1) stopped in tdb_event_create at 0xff38409c
0xff38409c: tdb_event_create       :    retl
Current function is main
216       stat = (int) thr_create(NULL, 0, consumer, q, tflags, &tid_cons2);
(dbx)

アプリケーションで新しいスレッドが発生しますが、それがスレッド t@1 ではなくスレッド t@5 から発生することがある場合は、次のようにするとそのイベントを獲得できます。


(dbx) stop thr_create -thread t@5

LWP 情報について

通常は LWP を意識する必要はありません。ただし、スレッドレベルでの問い合わせが完全にできない場合には、lwps コマンドを使用して、LWP に関する情報を入手できます。


(dbx) lwps
    l@1 running in main()
    l@2 running in sigwait()
    l@3 running in _lwp_sema_wait()
  *>l@4 breakpoint in Queue_dequeue()
    l@5 running in _thread_start()
(dbx)

LWP リストの各行の内容は、次のとおりです。

現在の LWP を表示または変更するには、lwp コマンド」を使用してください。