えー,こないだ本屋で初めて見たのだけど,CQ出版からgdbのTECH-Iが出ているね.
とりあえず買って内容を見てみたけど,スタブの移植の話題もあり, うん,ためになる.
あと 30日でできる! OS自作入門 というわりと有名な本があって,買ってみた.うーん,ためになるなあ. というか著者の「あんまり難しいこと考えないでパッと面白いもの作ってみよう」 的な考え方には賛成だなあ.まあ実際のところ,OSを作るのはそんなに難しくは ないとは思うのだけど,細かいことを考え出したらきりがない. なので,とりあえず動くものを作ってしまっていじりながら勉強していく, というのはとてもいいと思う.
で,KOZOSなのだが,前回までで,スレッド情報が見れるようにはなった. が,せめてスレッド名くらいは出てくれないとさみしいよね. ということで,今回はスレッドの詳細情報の表示について.
前回実装しなかったコマンドとして
まあまずはいつもどおり,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つがある.
で,実装したのがこんな感じ.
(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 ってどういう意味なのだろう? カレントスレッドの情報を返せばいいような気もするが... まあこのへんはまた次回考えよう.