(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

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

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

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

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

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

前回実装しなかったコマンドとして
  • qThreadExtraInfo,80c4560
  • qP0000001f00000000080c4560
というのがあった.まあどちらもスレッドの詳細情報を取得するためのコマンドのようなのだが,前回ではとりあえず未実装として「$#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つがある.
  • TAG_THREADID
  • TAG_EXISTS
  • TAG_THREADNAME
  • TAG_MOREDISPLAY
  • TAG_DISPLAY
まあパラメータのフォーマットに関しては,remote_unpack_thread_info_response() 内の各タグの解析部分を参照してほしい.

で,実装したのがこんな感じ.今回の差分は以下.

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 ってどういう意味なのだろう?カレントスレッドの情報を返せばいいような気もするが...まあこのへんはまた次回考えよう.