(第21回)GDBのスレッド対応(その4:スレッドの詳細情報表示)

2007/11/24

あなたは 人目のお客様です.

えー,こないだ本屋で初めて見たのだけど,CQ出版からgdbのTECH-Iが出ているね.

「GDBを使った実践的デバッグ手法」

とりあえず買って内容を見てみたけど,スタブの移植の話題もあり, うん,ためになる.

あと 30日でできる! OS自作入門 というわりと有名な本があって,買ってみた.うーん,ためになるなあ. というか著者の「あんまり難しいこと考えないでパッと面白いもの作ってみよう」 的な考え方には賛成だなあ.まあ実際のところ,OSを作るのはそんなに難しくは ないとは思うのだけど,細かいことを考え出したらきりがない. なので,とりあえず動くものを作ってしまっていじりながら勉強していく, というのはとてもいいと思う.

で,KOZOSなのだが,前回までで,スレッド情報が見れるようにはなった. が,せめてスレッド名くらいは出てくれないとさみしいよね. ということで,今回はスレッドの詳細情報の表示について.

前回実装しなかったコマンドとして

というのがあった.まあどちらもスレッドの詳細情報を取得するためのコマンドの ようなのだが,前回ではとりあえず未実装として「$#00」を返していたために, gdb側ではスレッド名などを表示できないでいた.今回はこのへんを実装してみる.

まあまずはいつもどおり,remote.cを読んで みよう.

前回もちょっと説明したが,これらのコマンドは remote.c の qThreadExtraInfo は remote_threads_extra_info() で, qP は pack_threadinfo_request() で発行されている. で,これも前回説明したけど remote_threads_extra_info() で,qThreadExtraInfo に失敗した際に remote_get_threadinfo() を経由して pack_threadinfo_request() が呼ばれている. ということは,「qThreadExtraInfo」は新しいスレッド情報取得コマンド, 「qP」レガシーなスレッド情報取得コマンドだと思われる. ということで,とりあえず「qP」に応答するように実装してみよう.

で,すでに説明したが「qP」は remote_get_threadinfo() 経由で pack_threadinfo_request() で発行されている.

static int
remote_get_threadinfo (threadref *threadid, int fieldset,       /* TAG mask */
                       struct gdb_ext_thread_info *info)
{
  struct remote_state *rs = get_remote_state ();
  int result;

  pack_threadinfo_request (rs->buf, fieldset, threadid);
  putpkt (rs->buf);
  getpkt (&rs->buf, &rs->buf_size, 0);
  result = remote_unpack_thread_info_response (rs->buf + 2,
                                               threadid, info);
  return result;
}
「qP」の発行後に remote_unpack_thread_info_response() が呼ばれ, 応答が解析されるようだ. remote_get_threadinfo()の発行元である remote_threads_extra_info()では
static char *
remote_threads_extra_info (struct thread_info *tp)
{
...
  if (use_threadextra_query)
    {
      xsnprintf (rs->buf, get_remote_packet_size (), "qThreadExtraInfo,%x",
                 PIDGET (tp->ptid));
      putpkt (rs->buf);
      getpkt (&rs->buf, &rs->buf_size, 0);
      if (rs->buf[0] != 0)
...
のようにして,まずは「qThreadExtraInfo」を試した後, スタブ側で未実装(つまり,「$#00」が返ってきた)であるならば
  set = TAG_THREADID | TAG_EXISTS | TAG_THREADNAME
    | TAG_MOREDISPLAY | TAG_DISPLAY;
  int_to_threadref (&id, PIDGET (tp->ptid));
  if (remote_get_threadinfo (&id, set, &threadinfo))
    if (threadinfo.active)
      {
        if (*threadinfo.shortname)
          n += xsnprintf (&display_buf[0], sizeof (display_buf) - n,
                          " Name: %s,", threadinfo.shortname);
        if (*threadinfo.display)
          n += xsnprintf (&display_buf[n], sizeof (display_buf) - n,
                          " State: %s,", threadinfo.display);
        if (*threadinfo.more_display)
          n += xsnprintf (&display_buf[n], sizeof (display_buf) - n,
                          " Priority: %s", threadinfo.more_display);
のようにして remote_get_threadinfo() を呼び出して「qP」を発行, 応答を解析し,スレッド名,状態(State),優先度(Priority)を表示しているようだ.

ということで,まあ結論からすると 「qP」のgdb→スタブへの送信方法は pack_threadinfo_request(), スタブ→gdbの応答方法は remote_unpack_thread_info_response() を見ればよい.

static char *
pack_threadinfo_request (char *pkt, int mode, threadref *id)
{
  *pkt++ = 'q';				/* Info Query */
  *pkt++ = 'P';				/* process or thread info */
  pkt = pack_int (pkt, mode);		/* mode */
  pkt = pack_threadid (pkt, id);	/* threadid */
  *pkt = '\0';				/* terminate */
  return pkt;
}
前回は「qP」は
qP0000001f00000000080c4560
のように送信されていた.pack_threadinfo_request()を見ると, qPに続く16進文字の前半8桁は mode,後半16桁はスレッドIDということになる. スレッドIDは,おそらく情報を取得したいスレッドのスレッドIDだと想像がつく. 問題は mode だが,これは pack_threadinfo_request() の呼び出しを遡ると, remote_threads_extra_info() の内部で
  set = TAG_THREADID | TAG_EXISTS | TAG_THREADNAME
    | TAG_MOREDISPLAY | TAG_DISPLAY;
のようにして設定した値が渡されてくるようだ.

次にqPの応答解析部分.

static int
remote_unpack_thread_info_response (char *pkt, threadref *expectedref,
				    struct gdb_ext_thread_info *info)
{
  struct remote_state *rs = get_remote_state ();
  int mask, length;
  int tag;
  threadref ref;
  char *limit = pkt + rs->buf_size; /* Plausible parsing limit.  */
  int retval = 1;

  /* info->threadid = 0; FIXME: implement zero_threadref.  */
  info->active = 0;
  info->display[0] = '\0';
  info->shortname[0] = '\0';
  info->more_display[0] = '\0';

  /* Assume the characters indicating the packet type have been
     stripped.  */
  pkt = unpack_int (pkt, &mask);	/* arg mask */
  pkt = unpack_threadid (pkt, &ref);

  if (mask == 0)
    warning (_("Incomplete response to threadinfo request."));
  if (!threadmatch (&ref, expectedref))
    {			/* This is an answer to a different request.  */
      warning (_("ERROR RMT Thread info mismatch."));
      return 0;
    }
  copy_threadref (&info->threadid, &ref);

  /* Loop on tagged fields , try to bail if somthing goes wrong.  */

  /* Packets are terminated with nulls.  */
  while ((pkt < limit) && mask && *pkt)
    {
      pkt = unpack_int (pkt, &tag);	/* tag */
      pkt = unpack_byte (pkt, &length);	/* length */
      if (!(tag & mask))		/* Tags out of synch with mask.  */
	{
	  warning (_("ERROR RMT: threadinfo tag mismatch."));
	  retval = 0;
	  break;
	}
      if (tag == TAG_THREADID)
	{
	  if (length != 16)
	    {
	      warning (_("ERROR RMT: length of threadid is not 16."));
	      retval = 0;
	      break;
	    }
	  pkt = unpack_threadid (pkt, &ref);
	  mask = mask & ~TAG_THREADID;
	  continue;
	}
      if (tag == TAG_EXISTS)
	{
	  info->active = stub_unpack_int (pkt, length);
	  pkt += length;
	  mask = mask & ~(TAG_EXISTS);
	  if (length > 8)
	    {
	      warning (_("ERROR RMT: 'exists' length too long."));
	      retval = 0;
	      break;
	    }
	  continue;
	}
      if (tag == TAG_THREADNAME)
	{
	  pkt = unpack_string (pkt, &info->shortname[0], length);
	  mask = mask & ~TAG_THREADNAME;
	  continue;
	}
      if (tag == TAG_DISPLAY)
	{
	  pkt = unpack_string (pkt, &info->display[0], length);
	  mask = mask & ~TAG_DISPLAY;
	  continue;
	}
      if (tag == TAG_MOREDISPLAY)
	{
	  pkt = unpack_string (pkt, &info->more_display[0], length);
	  mask = mask & ~TAG_MOREDISPLAY;
	  continue;
	}
      warning (_("ERROR RMT: unknown thread info tag."));
      break;			/* Not a tag we know about.  */
    }
  return retval;
}
まず
  pkt = unpack_int (pkt, &mask);	/* arg mask */
  pkt = unpack_threadid (pkt, &ref);
という部分で,16進数の先頭8桁を mask,その後の16桁をスレッドIDとして 解析している.その後
  if (!threadmatch (&ref, expectedref))
のようにしてスレッドIDをチェックしているので,スレッドIDにはスタブ側に渡された 値をそのまま返さないといけないようだ.

次に,

  /* Packets are terminated with nulls.  */
  while ((pkt < limit) && mask && *pkt)
    {
のようにして,コマンドの内容を順次解析していく. 続くコマンドの内容は
      pkt = unpack_int (pkt, &tag);	/* tag */
      pkt = unpack_byte (pkt, &length);	/* length */
      if (!(tag & mask))		/* Tags out of synch with mask.  */
	{
	  warning (_("ERROR RMT: threadinfo tag mismatch."));
	  retval = 0;
	  break;
	}
のようにして,まず16進で8桁を tag,続く2桁(1バイトの値)を length として 取得している. さらに
      if (tag == TAG_THREADID)
	{
...
のようにして,tag に応じた値を順次取得していく. まあ解説が面倒なので結論から説明してしまうと, gdb側では欲しい情報を「qP」発行時に mode としてビットマスクで渡し, スタブ側では要求された情報をコマンドに順次詰めて返す (この際に,tag, length, パラメータの順に格納する)ようだ. 取得できる情報には,以下の5つがある. まあパラメータのフォーマットに関しては, remote_unpack_thread_info_response() 内の各タグの解析部分を参照してほしい.

で,実装したのがこんな感じ.

(2009/04/10 ライセンスに関する文書として,KL-01とLICENSEを追加. 詳しくは第43回を参照)

今回の差分は以下.

diff -ruN kozos20/i386-stub.c kozos21/i386-stub.c
--- kozos20/i386-stub.c	Sat Nov 24 09:58:09 2007
+++ kozos21/i386-stub.c	Sat Nov 24 11:35:14 2007
@@ -1015,6 +1015,69 @@
 		ptr = intNToHex(ptr, (int)gen_thread->id, 4);
 	      }
 	      break;
+	    case 'P':
+	      {
+		int mode;
+		unsigned int threadid[2];
+		kz_thread *thp;
+
+#define TAG_THREADID 1
+#define TAG_EXISTS 2
+#define TAG_DISPLAY 4
+#define TAG_THREADNAME 8
+#define TAG_MOREDISPLAY 16
+		mode = hexToIntN(&ptr, 4);
+		threadid[0] = hexToIntN(&ptr, 4);
+		threadid[1] = hexToIntN(&ptr, 4);
+		thp = (kz_thread *)threadid[1];
+
+		ptr = remcomOutBuffer;
+		*ptr++ = 'Q';
+		*ptr++ = 'P';
+		ptr = intNToHex(ptr, mode, 4);
+		ptr = intNToHex(ptr, threadid[0], 4);
+		ptr = intNToHex(ptr, threadid[1], 4);
+
+		if (mode & TAG_THREADID) {
+		  ptr = intNToHex(ptr, TAG_THREADID, 4); /* mode */
+		  ptr = intNToHex(ptr, 16, 1); /* length */
+		  ptr = intNToHex(ptr, threadid[0], 4);
+		  ptr = intNToHex(ptr, threadid[1], 4);
+		}
+		if (mode & TAG_EXISTS) {
+		  ptr = intNToHex(ptr, TAG_EXISTS, 4); /* mode */
+		  ptr = intNToHex(ptr, 1, 1); /* length */
+		  *ptr++ = '1';
+		}
+		if (mode & TAG_DISPLAY) {
+		  ptr = intNToHex(ptr, TAG_DISPLAY, 4); /* mode */
+		  ptr = intNToHex(ptr, 3, 1); /* length */
+		  {
+		    kz_thread *thp2;
+		    strcpy(ptr, "SLP");
+		    for (thp2 = readyque[thp->pri]; thp2; thp2 = thp2->next) {
+		      if (thp == thp2) {
+			strcpy(ptr, "RUN");
+			break;
+		      }
+		    }
+		    ptr += strlen(ptr);
+		  }
+		}
+		if (mode & TAG_THREADNAME) {
+		  ptr = intNToHex(ptr, TAG_THREADNAME, 4); /* mode */
+		  ptr = intNToHex(ptr, strlen(thp->name), 1); /* length */
+		  strcpy(ptr, thp->name);
+		  ptr += strlen(thp->name);
+		}
+		if (mode & TAG_MOREDISPLAY) {
+		  ptr = intNToHex(ptr, TAG_MOREDISPLAY, 4); /* mode */
+		  ptr = intNToHex(ptr, 2, 1); /* length */
+		  ptr = intNToHex(ptr, thp->pri, 1);
+		}
+		*ptr = '\0';
+	      }
+	      break;
 	    default:
 	      break;
 	    }
diff -ruN kozos20/thread.h kozos21/thread.h
--- kozos20/thread.h	Sat Nov 24 09:58:09 2007
+++ kozos21/thread.h	Sat Nov 24 11:26:13 2007
@@ -40,6 +40,7 @@
 } kz_thread;
 
 extern kz_thread threads[THREAD_NUM];
+extern kz_thread *readyque[PRI_NUM];
 extern kz_thread *current;
 extern sigset_t block;
 
「qP」に対してスレッド情報を返信している. まあパラメータの細かいフォーマットについては, remote_unpack_thread_info_response()での解析処理を参照してほしい.

remote_threads_extra_info() を見たところ, TAG_DISPLAY はスレッドの状態(スリープ,ランニング), TAG_THREADNAME はスレッド名, TAG_MOREDISPLAY は優先度として表示するようなので,そーいうふうに応答している. 内部で strlen() を使ってしまっていて,スタブ内部からライブラリ関数を呼ぶのは 実はあまりよくない(第10回参照)のだけど,面倒なのでご愛敬.

では,動作させてみよう.いつもどおり実行形式を起動,gdbで接続,continue, Ctrl-Cでブレークする.で,info threads を実行.

おー,スレッド名とかが表示されている.

このときの通信内容は以下.

[$qfThreadInfo#bb](+)($#00)[+]
[$qL1200000000000000000#50](+)($qM010000000000000000000000000080c4480#9a)[+]
[$qL02000000000080c4480#9a](+)($qM01000000000080c448000000000080c4780#e8)[+]
[$qL02000000000080c4780#9d](+)($qM01000000000080c478000000000080c4a80#15)[+]
[$qL02000000000080c4a80#c7](+)($qM01000000000080c4a8000000000080c4d80#42)[+]
[$qL02000000000080c4d80#ca](+)($qM01000000000080c4d8000000000080c5080#12)[+]
[$qL02000000000080c5080#97](+)($qM01000000000080c508000000000080c5380#e2)[+]
[$qL02000000000080c5380#9a](+)($qM01000000000080c538000000000080c5680#e8)[+]
[$qL02000000000080c5680#9d](+)($qM01100000000080c5680#9e)[+]
[$qThreadExtraInfo,80c5680#23](+)($#00)[+]
[$qP0000001f00000000080c5680#c6](+)($QP0000001f00000000080c5680000000011000000000080c5680000000020110000000403SLP0000000805httpd000000100209#1d)[+]
[$Hg80c5680#4d](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000080560c08fcc5110818c61108ecc7110800000000ebed050802020000330000003b0000003b0000003b0000003b0000001b000000#49)[+]
[$qP0000001f00000000080c5380#c3](+)($QP0000001f00000000080c5380000000011000000000080c5380000000020110000000403SLP0000000807telnetd000000100208#e4)[+]
[$Hg80c5380#4a](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000080530c08fc35110818361108ec37110800000000ebed050802020000330000003b0000003b0000003b0000003b0000001b000000#b6)[+]
[$qP0000001f00000000080c5080#c0](+)($QP0000001f00000000080c5080000000011000000000080c5080000000020110000000403SLP0000000805clock000000100207#f7)[+]
[$Hg80c5080#47](+)($OK#9a)[+]
[$g#67](+)($0000000098b00d08ea04000080500c083ca7100858a71008eca7100800000000ebed050806020000330000003b0000003b0000003b0000003b0000001b000000#91)[+]
[$qP0000001f00000000080c4d80#f3](+)($QP0000001f00000000080c4d80000000011000000000080c4d80000000020110000000403RUN0000000804idle00000010021f#24)[+]
[$Hg80c4d80#7a](+)($OK#9a)[+]
[$g#67](+)($0400000004000000ffffffff804d0c088c171008b8171008ec171008000000008f08060813020000330000003b0000003b0000003b0000003b0000001b000000#aa)[+]
[$qP0000001f00000000080c4a80#f0](+)($QP0000001f00000000080c4a80000000011000000000080c4a80000000020110000000403SLP0000000806outlog000000100203#e2)[+]
[$Hg80c4a80#77](+)($OK#9a)[+]
[$g#67](+)($0000000060800f08ea040000804a0c083c870f0858870f08ec870f0800000000ebed050806020000330000003b0000003b0000003b0000003b0000001b000000#b2)[+]
[$qP0000001f00000000080c4780#c6](+)($QP0000001f00000000080c4780000000011000000000080c4780000000020110000000403RUN0000000805stubd000000100202#1a)[+]
[$Hg80c4780#4d](+)($OK#9a)[+]
[$g#67](+)($00880d0800000000ea04000080470c0858f60e0858f60e08ecf70e080000000099ae040802020000330000003b0000003b0000003b0000003b0000001b000000#87)[+]
[$qP0000001f00000000080c4480#c3](+)($QP0000001f00000000080c4480000000011000000000080c4480000000020110000000403SLP0000000807extintr000000100201#fb)[+]
[$Hg80c4480#4a](+)($OK#9a)[+]
[$g#67](+)($0000000006000000ea04000080440c086c660e0888660e08ec670e0800000000ebed050806020000330000003b0000003b0000003b0000003b0000001b000000#3a)[+]
[$Hg80c4780#4d](+)($OK#9a)[+]
[$g#67](+)($00880d0800000000ea04000080470c0858f60e0858f60e08ecf70e080000000099ae040802020000330000003b0000003b0000003b0000003b0000001b000000#87)[+]
(この状態で停止)
「qP」に対して「QP」でスレッド情報が返されていることに注目. 問題なく動作しているようだ.

しかしここでもう一度 info threads を行うと,なんか固まってしまうようだ.

このときの通信内容は以下.

[$T080c5680#22](+)($#00)[+]
[$T080c5380#1f](+)($#00)[+]
[$T080c5080#1c](+)($#00)[+]
[$T080c4d80#4f](+)($#00)[+]
[$T080c4a80#4c](+)($#00)[+]
[$T080c4780#22](+)($#00)[+]
[$T080c4480#1f](+)($#00)[+]
[$qL120f0000000080c5680#d4](+)($qM011f0000000080c5680#d4)[+]
[$qP0000001f00000000ffffffff#28](+)
(この状態で停止)
どうも「qP」コマンドでスレッドIDが 0xffffffff として渡されているため, スレッドの検索に失敗しているようだ.

うーん,スレッドIDが 0xffffffff ってどういう意味なのだろう? カレントスレッドの情報を返せばいいような気もするが... まあこのへんはまた次回考えよう.


メールは kozos(アットマーク)kozos.jp まで