Red Hat Training

A Red Hat training course is available for Red Hat Enterprise Linux

5.3.5. 個別スレッドのデバッグ

GDB には、個別のスレッドをデバッグし、それらを個別に操作および検査する機能があります。この機能はデフォルトでは有効ではありません。これを実行するには、set non-stop on および set target-async on を使用します。これらは .gdbinit に追加できます。この機能がオンになると、GDB はスレッドデバッグを実行する準備ができます。
たとえば、以下のプログラムは 2 つのスレッドを作成します。これらの 2 つのスレッドとメインを実行する元のスレッドと合わせると、合計 3 つのスレッドになります。
three-threads.c

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_t thread;

void* thread3 (void* d)
{
  int count3 = 0;

  while(count3 < 1000){
    sleep(10);
    printf("Thread 3: %d\n", count3++);
  }
  return NULL;
}

void* thread2 (void* d)
{
  int count2 = 0;

  while(count2 < 1000){
    printf("Thread 2: %d\n", count2++);
  }
  return NULL;
}

int main (){

  pthread_create (&thread, NULL, thread2, NULL);
  pthread_create (&thread, NULL, thread3, NULL);
  
  //Thread 1
  int count1 = 0;

  while(count1 < 1000){
    printf("Thread 1: %d\n", count1++);
  }

  pthread_join(thread,NULL);
  return 0;
}

これを GDB で検査するためにこのプログラムをコンパイルします。
gcc -g three-threads.c -o three-threads  -lpthread
gdb ./three-threads
まず、すべてのスレッド関数 thread1、thread2、およびメインにブレークポイントを設定します。
(gdb) break thread3
Breakpoint 1 at 0x4006c0: file three-threads.c, line 9.
(gdb) break thread2
Breakpoint 2 at 0x40070c: file three-threads.c, line 20.
(gdb) break main
Breakpoint 3 at 0x40074a: file three-threads.c, line 30.
次に、プログラムを実行します。
(gdb) run
[...]
Breakpoint 3, main () at three-threads.c:30
30	  pthread_create (&thread, NULL, thread2, NULL);
[...]
(gdb) info threads
* 1 Thread 0x7ffff7fd5720 (LWP 4620)  main () at three-threads.c:30
(gdb) 

コマンド info threads がプログラムのスレッド要約と現在の状態についての詳細情報の一部を提供することに注意してください。この場合、作成されているのは 1 つのスレッドのみになります。
さらに実行を継続します。
(gdb) next
[New Thread 0x7ffff7fd3710 (LWP 4687)]
31	  pthread_create (&thread, NULL, thread3, NULL);
(gdb) 
Breakpoint 2, thread2 (d=0x0) at three-threads.c:20
20	  int count2 = 0;
next
[New Thread 0x7ffff75d2710 (LWP 4688)]
34	  int count1 = 0;
(gdb) 
Breakpoint 1, thread3 (d=0x0) at three-threads.c:9
9	  int count3 = 0;
info threads
  3 Thread 0x7ffff75d2710 (LWP 4688)  thread3 (d=0x0) at three-threads.c:9
  2 Thread 0x7ffff7fd3710 (LWP 4687)  thread2 (d=0x0) at three-threads.c:20
* 1 Thread 0x7ffff7fd5720 (LWP 4620)  main () at three-threads.c:34

ここで、2 つのスレッドがさらに作成されました。スターマークの付いたスレッドは、現在フォーカスされているスレッドであることを示しています。また、新たに作成されたスレッドの thread2() と thread3() は、初期化関数でそれらに対して設定されたブレークポイントにヒットしました。
実際のスレッドデバッグを開始するには、thread <thread number> コマンドを使用してフォーカスを別のスレッドに切り替えます。
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff7fd3710 (LWP 4687))]#0  thread2 (d=0x0)
    at three-threads.c:20
20	  int count2 = 0;
(gdb) list
15	  return NULL;
16	}
17	
18	void* thread2 (void* d)
19	{
20	  int count2 = 0;
21	
22	  while(count2 < 1000){
23	    printf("Thread 2: %d\n", count2++);
24	  }
Thread 2 は、その関数 thread2() の 20 行目で停止しました。
(gdb) next
22	  while(count2 < 1000){
(gdb) print count2
$1 = 0
(gdb) next
23	    printf("Thread 2: %d\n", count2++);
(gdb) next
Thread 2: 0
22	  while(count2 < 1000){
(gdb) next
23	    printf("Thread 2: %d\n", count2++);
(gdb) print count2
$2 = 1
(gdb) info threads
  3 Thread 0x7ffff75d2710 (LWP 4688)  thread3 (d=0x0) at three-threads.c:9
* 2 Thread 0x7ffff7fd3710 (LWP 4687)  thread2 (d=0x0) at three-threads.c:23
  1 Thread 0x7ffff7fd5720 (LWP 4620)  main () at three-threads.c:34
(gdb) 
上記では、thread2 の数行がカウンターの count2 を出力しており、'info threads' の出力に表示されるように thread 2 が 23 行目に置かれています。
次は thread3 です。
(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff75d2710 (LWP 4688))]#0  thread3 (d=0x0)
    at three-threads.c:9
9	  int count3 = 0;
(gdb) list
4	
5	pthread_t thread;
6	
7	void* thread3 (void* d)
8	{
9	  int count3 = 0;
10	
11	  while(count3 < 1000){
12	    sleep(10);
13	    printf("Thread 3: %d\n", count3++);
(gdb) 
Thread 3 は、Sleep ステートメントがあり、実行スピードが遅くなるという点で少し異なっています。これは、無視される IO スレッドの表現として考えてください。このスレッドは無視されるため、その実行は、continue を使用して中断せずに続行されます。
(gdb) continue &
(gdb) Thread 3: 0
Thread 3: 1
Thread 3: 2
Thread 3: 3
continue の末尾にある & に注意してください。これによって、GDB のプロンプトが戻るため、他のコマンドを実行することができます。interrupt を使用して thread 3 が再び関連性を持つ場合は、実行が停止される可能性があります。
(gdb) interrupt
[Thread 0x7ffff75d2710 (LWP 4688)] #3 stopped.
0x000000343f4a6a6d in nanosleep () at ../sysdeps/unix/syscall-template.S:82
82	T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
また、元のメインスレッドに戻り、これをさらに検査することも可能です。
(gdb) thread 1
[Switching to thread 1 (Thread 0x7ffff7fd5720 (LWP 4620))]#0  main ()
    at three-threads.c:34
34	  int count1 = 0;
(gdb) next
36	  while(count1 < 1000){
(gdb) next
37	    printf("Thread 1: %d\n", count1++);
(gdb) next
Thread 1: 0
36	  while(count1 < 1000){
(gdb) next
37	    printf("Thread 1: %d\n", count1++);
(gdb) next
Thread 1: 1
36	  while(count1 < 1000){
(gdb) next
37	    printf("Thread 1: %d\n", count1++);
(gdb) next
Thread 1: 2
36	  while(count1 < 1000){
(gdb) print count1 
$3 = 3
(gdb) info threads 
  3 Thread 0x7ffff75d2710 (LWP 4688)  0x000000343f4a6a6d in nanosleep ()
    at ../sysdeps/unix/syscall-template.S:82
  2 Thread 0x7ffff7fd3710 (LWP 4687)  thread2 (d=0x0) at three-threads.c:23
* 1 Thread 0x7ffff7fd5720 (LWP 4620)  main () at three-threads.c:36
(gdb) 
情報スレッドの出力からも分かるように、他のスレッドはそれらが置かれた場所にあるため、thread 1 のデバッグの影響は受けません。