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

前回まででスレッド対応は完了のはずだったのだけど,ちょっとバグがあったので修正.

前回のソースコードで info threads を2回実行すると,以下のようになる.

(gdb) info threads
7 Thread 135026528 ( Name: httpd, State: SLP, Priority: 09) 0x0805eeb7 in kill ()
6 Thread 135025760 ( Name: telnetd, State: SLP, Priority: 08) 0x0805eeb7 in kill ()
5 Thread 135024992 ( Name: clock, State: SLP, Priority: 07) 0x0805eeb7 in kill ()
4 Thread 135024224 ( Name: idle, State: RUN, Priority: 1f) 0x0806095b in select ()
3 Thread 135023456 ( Name: outlog, State: SLP, Priority: 03) 0x0805eeb7 in kill ()
2 Thread 135021920 ( Name: extintr, State: SLP, Priority: 01) 0x0805eeb7 in kill ()
* 1 Thread 135022688 ( Name: stubd, State: RUN, Priority: 02) breakpoint ()
at i386-stub.c:1187
(gdb) info threads
8 Thread -1 ( Name: stubd, State: RUN, Priority: 02) breakpoint ()
at i386-stub.c:1187
7 Thread 135026528 ( Name: httpd, State: SLP, Priority: 09) 0x0805eeb7 in kill ()
6 Thread 135025760 ( Name: telnetd, State: SLP, Priority: 08) 0x0805eeb7 in kill ()
5 Thread 135024992 ( Name: clock, State: SLP, Priority: 07) 0x0805eeb7 in kill ()
4 Thread 135024224 ( Name: idle, State: RUN, Priority: 1f) 0x0806095b in select ()
3 Thread 135023456 ( Name: outlog, State: SLP, Priority: 03) 0x0805eeb7 in kill ()
2 Thread 135021920 ( Name: extintr, State: SLP, Priority: 01) 0x0805eeb7 in kill ()
* 1 Thread 135022688 ( Name: stubd, State: RUN, Priority: 02) breakpoint ()
at i386-stub.c:1187
(gdb)

2回目の info threads で,スレッド番号8としてなんかスレッドIDが-1のstubd というのが出てしまっている.stubd はスレッド番号1ですでに出ているので,これは明らかにおかしい.

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

($T054:58f60e08;5:58f60e08;8:65af0408;thread:080c4860;#7d)[+]
[$M8048088,1:55#c2](+)($OK#9a)[+]
[$T080c4860#21](+)($OK#9a)[+]
[$qfThreadInfo#bb](+)($#00)[+]
[$qL1200000000000000000#50](+)($qM010000000000000000000000000080c4560#99)[+]
[$qL02000000000080c4560#99](+)($qM01000000000080c456000000000080c4860#e6)[+]
[$qL02000000000080c4860#9c](+)($qM01000000000080c486000000000080c4b60#13)[+]
[$qL02000000000080c4b60#c6](+)($qM01000000000080c4b6000000000080c4e60#40)[+]
[$qL02000000000080c4e60#c9](+)($qM01000000000080c4e6000000000080c5160#10)[+]
[$qL02000000000080c5160#96](+)($qM01000000000080c516000000000080c5460#e0)[+]
[$qL02000000000080c5460#99](+)($qM01000000000080c546000000000080c5760#e6)[+]
[$qL02000000000080c5760#9c](+)($qM01100000000080c5760#9d)[+]
[$qThreadExtraInfo,80c5760#22](+)($#00)[+]
[$qP0000001f00000000080c5760#c5](+)($QP0000001f00000000080c5760000000011000000000080c5760000000020110000000403SLP0000000805httpd000000100209#1b)[+]
[$Hg80c5760#4c](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000060570c08fcc5110818c61108ecc7110800000000b7ee050802020000330000003b0000003b0000003b0000003b0000001b000000#1b)[+]
[$qP0000001f00000000080c5460#c2](+)($QP0000001f00000000080c5460000000011000000000080c5460000000020110000000403SLP0000000807telnetd000000100208#e2)[+]
[$Hg80c5460#49](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000060540c08fc35110818361108ec37110800000000b7ee050802020000330000003b0000003b0000003b0000003b0000001b000000#88)[+]
[$qP0000001f00000000080c5160#bf](+)($QP0000001f00000000080c5160000000011000000000080c5160000000020110000000403SLP0000000805clock000000100207#f5)[+]
[$Hg80c5160#46](+)($OK#9a)[+]
[$g#67](+)($0000000098b00d08ea04000060510c083ca7100858a71008eca7100800000000b7ee050806020000330000003b0000003b0000003b0000003b0000001b000000#63)[+]
[$qP0000001f00000000080c4e60#f2](+)($QP0000001f00000000080c4e60000000011000000000080c4e60000000020110000000403RUN0000000804idle00000010021f#22)[+]
[$Hg80c4e60#79](+)($OK#9a)[+]
[$g#67](+)($0400000004000000ffffffff604e0c088c171008b8171008ec171008000000005b09060813020000330000003b0000003b0000003b0000003b0000001b000000#a3)[+]
[$qP0000001f00000000080c4b60#ef](+)($QP0000001f00000000080c4b60000000011000000000080c4b60000000020110000000403SLP0000000806outlog000000100203#e0)[+]
[$Hg80c4b60#76](+)($OK#9a)[+]
[$g#67](+)($0000000060800f08ea040000604b0c083c870f0858870f08ec870f0800000000b7ee050806020000330000003b0000003b0000003b0000003b0000001b000000#84)[+]
[$qP0000001f00000000080c4560#c2](+)($QP0000001f00000000080c4560000000011000000000080c4560000000020110000000403SLP0000000807extintr000000100201#f9)[+]
[$Hg80c4560#49](+)($OK#9a)[+]
[$g#67](+)($0000000006000000ea04000060450c086c660e0888660e08ec670e0800000000b7ee050806020000330000003b0000003b0000003b0000003b0000001b000000#0c)[+]
[$qP0000001f00000000080c4860#c5](+)($QP0000001f00000000080c4860000000011000000000080c4860000000020110000000403RUN0000000805stubd000000100202#18)[+]
[$Hg80c4860#4c](+)($OK#9a)[+]
[$g#67](+)($00880d0800000000ea04000060480c0858f60e0858f60e08ecf70e080000000065af040802020000330000003b0000003b0000003b0000003b0000001b000000#80)[+]
[$T080c5760#21](+)($OK#9a)[+]
[$T080c5460#1e](+)($OK#9a)[+]
[$T080c5160#1b](+)($OK#9a)[+]
[$T080c4e60#4e](+)($OK#9a)[+]
[$T080c4b60#4b](+)($OK#9a)[+]
[$T080c4560#1e](+)($OK#9a)[+]
[$T080c4860#21](+)($OK#9a)[+]
[$qL120f0000000080c5760#d3](+)($qM011f0000000080c5760#d3)[+]
[$qP0000001f00000000ffffffff#28](+)($QP0000001f00000000ffffffff000000011000000000080c4860000000020110000000403RUN0000000805stubd000000100202#7b)[+]
[$Hg-1#0d](+)($OK#9a)[+]
[$g#67](+)($00880d0800000000ea04000060480c0858f60e0858f60e08ecf70e080000000065af040802020000330000003b0000003b0000003b0000003b0000001b000000#80)[+]
[$qP0000001f00000000080c5760#c5](+)($QP0000001f00000000080c5760000000011000000000080c5760000000020110000000403SLP0000000805httpd000000100209#1b)[+]
[$Hg80c5760#4c](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000060570c08fcc5110818c61108ecc7110800000000b7ee050802020000330000003b0000003b0000003b0000003b0000001b000000#1b)[+]
[$qP0000001f00000000080c5460#c2](+)($QP0000001f00000000080c5460000000011000000000080c5460000000020110000000403SLP0000000807telnetd000000100208#e2)[+]
[$Hg80c5460#49](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000060540c08fc35110818361108ec37110800000000b7ee050802020000330000003b0000003b0000003b0000003b0000001b000000#88)[+]
[$qP0000001f00000000080c5160#bf](+)($QP0000001f00000000080c5160000000011000000000080c5160000000020110000000403SLP0000000805clock000000100207#f5)[+]
[$Hg80c5160#46](+)($OK#9a)[+]
[$g#67](+)($0000000098b00d08ea04000060510c083ca7100858a71008eca7100800000000b7ee050806020000330000003b0000003b0000003b0000003b0000001b000000#63)[+]
[$qP0000001f00000000080c4e60#f2](+)($QP0000001f00000000080c4e60000000011000000000080c4e60000000020110000000403RUN0000000804idle00000010021f#22)[+]
[$Hg80c4e60#79](+)($OK#9a)[+]
[$g#67](+)($0400000004000000ffffffff604e0c088c171008b8171008ec171008000000005b09060813020000330000003b0000003b0000003b0000003b0000001b000000#a3)[+]
[$qP0000001f00000000080c4b60#ef](+)($QP0000001f00000000080c4b60000000011000000000080c4b60000000020110000000403SLP0000000806outlog000000100203#e0)[+]
[$Hg80c4b60#76](+)($OK#9a)[+]
[$g#67](+)($0000000060800f08ea040000604b0c083c870f0858870f08ec870f0800000000b7ee050806020000330000003b0000003b0000003b0000003b0000001b000000#84)[+]
[$qP0000001f00000000080c4560#c2](+)($QP0000001f00000000080c4560000000011000000000080c4560000000020110000000403SLP0000000807extintr000000100201#f9)[+]
[$Hg80c4560#49](+)($OK#9a)[+]
[$g#67](+)($0000000006000000ea04000060450c086c660e0888660e08ec670e0800000000b7ee050806020000330000003b0000003b0000003b0000003b0000001b000000#0c)[+]
[$qP0000001f00000000080c4860#c5](+)($QP0000001f00000000080c4860000000011000000000080c4860000000020110000000403RUN0000000805stubd000000100202#18)[+]
[$Hg80c4860#4c](+)($OK#9a)[+]
[$g#67](+)($00880d0800000000ea04000060480c0858f60e0858f60e08ecf70e080000000065af040802020000330000003b0000003b0000003b0000003b0000001b000000#80)[+]
(この状態で停止)

通信内容をよく見ると,

[$qP0000001f00000000ffffffff#28](+)($QP0000001f00000000ffffffff000000011000000000080c4860000000020110000000403RUN0000000805stubd000000100202#7b)[+]
[$Hg-1#0d](+)($OK#9a)[+]

という通信が行われていて,スレッドIDが-1でqPコマンドが発行されているんだな.で,その直後にやはりスレッドIDが-1でHgコマンドが発行されている.

Interface誌の今月号(2007/12)を見ると,HgコマンドがスレッドIDを-1で発行する際には,どうもすべてのスレッドに対して処理を行う,という意味になるらしい.あと remote.c の内部では,Hgコマンドは set_thread() によって発行されるのだけど,

set_thread (-1, 0);

のようにして,スレッドIDを-1として発行している部分がある.まあ上の場合は第2引数がゼロなので,HgではなくHcコマンドが発行されるのだが,スレッドIDが-1というのはやはり特別な意味を持っているらしく,このへんがあやしい.

...とおもっていろいろ調べたり,スレッドIDが-1の場合のqPコマンドやHgコマンドの動作をいろいろ変えたりして試してみたのだけど,おかしいまま.うーんへんだ.

どうもqPがスレッドIDを-1で発行されること自体,おかしな気がする.それでもってそれがまた成功してしまっているので,その後に Hg も -1 で発行されてしまっているのではなかろうか?

で,スレッド関連の通信内容をもう一度チェック.とくに,多少複雑なプロトコルになっているqLコマンドとかをよく見直す.そしたら以下のあやしい部分を発見.

[$qL1200000000000000000#50](+)($qM010000000000000000000000000080c4560#99)[+]
[$qL02000000000080c4560#99](+)($qM01000000000080c456000000000080c4860#e6)[+]
[$qL02000000000080c4860#9c](+)($qM01000000000080c486000000000080c4b60#13)[+]
[$qL02000000000080c4b60#c6](+)($qM01000000000080c4b6000000000080c4e60#40)[+]
[$qL02000000000080c4e60#c9](+)($qM01000000000080c4e6000000000080c5160#10)[+]
[$qL02000000000080c5160#96](+)($qM01000000000080c516000000000080c5460#e0)[+]
[$qL02000000000080c5460#99](+)($qM01000000000080c546000000000080c5760#e6)[+]
[$qL02000000000080c5760#9c](+)($qM01100000000080c5760#9d)[+]

最後のqLに対するqMの応答は,もうそれ以上スレッドが無いため,スレッドIDが空となって返っている.しかし応答では qM011... となっており,doneフラグは立っているのだが,スレッド数が1のままなんだな.i386-stub.c のqLコマンド応答部分を見てみると

case 'q':
switch (*ptr++)
{
case 'L':
{
int startflag, doneflag, countmax, count = 1, i;
...
ptr = remcomOutBuffer;
*ptr++ = 'q';
*ptr++ = 'M';
*ptr++ = hexchars[count >> 4];
*ptr++ = hexchars[count & 0xf];
*ptr++ = doneflag ? '1' : '0';

となっており,スレッド数である count が無条件で1になっている.これはまずい.

ということは,qPがスレッドIDを0xffffffff(つまり,-1)で発行されることと,HgがスレッドIDを-1で発行されることの対処を前回入れているが,実は上記 count がおかしいためにへんなスレッドIDのスレッドが登録されてしまっているのがそもそもの原因なのではなかろうか? count に対する修正を入れれば,このへんの対処は不要なのではなかろうか.

で,修正したのがこんな感じ.前回からの差分については diff.txt 参照.

では修正内容について説明しよう.まずqLコマンドの応答部分.

diff -ruN kozos22/i386-stub.c kozos23/i386-stub.c
--- kozos22/i386-stub.c Sat Nov 24 14:39:50 2007
+++ kozos23/i386-stub.c Sat Nov 24 17:45:25 2007
@@ -986,7 +986,7 @@
{
case 'L':
{
- int startflag, doneflag, countmax, count = 1, i;
+ int startflag, doneflag, countmax, count, i;
unsigned int threadid[2];
kz_thread *thp;

@@ -1008,6 +1008,9 @@
}
}

+ count = 1;
+ if (doneflag) count = 0;
+
ptr = remcomOutBuffer;
*ptr++ = 'q';
*ptr++ = 'M';

スレッド数を,最後のスレッドの場合はゼロにするように修正してある.

さらに,qPコマンドの応答部分.

@@ -1048,16 +1051,8 @@
mode = hexToIntN(&ptr, 4);
threadid[0] = hexToIntN(&ptr, 4);
threadid[1] = hexToIntN(&ptr, 4);
- if (threadid[1] == 0xffffffff)
- {
- /*
- * 何を返すべきかちょっと不明なので,とりあえず
- * カレントスレッドを返す.
- */
- thp = gen_thread; /* current を返すべきか? 不明... */
- } else {
- thp = (kz_thread *)threadid[1];
- }
+
+ thp = (kz_thread *)threadid[1];

ptr = remcomOutBuffer;
*ptr++ = 'Q';

たぶん今回のqLコマンドのバグ修正により,スレッドIDが0xffffffffでqPが発行されるようなことは無くなると思うので,その場合の対処を削除.まあこんなへんな処理,残しといてもしょうがないしね.

次に,Hgコマンドの応答部分の修正.

@@ -1125,8 +1120,11 @@
if (hexToInt(&ptr, &val))
{
if (rev) val = -val;
+ if (val == -1)
+ {
+ break;
+ }
stub_restore_regs(gen_thread);
- if (val == -1) val = (int)current;
gen_thread = (kz_thread *)val;
stub_store_regs(gen_thread);
strcpy (remcomOutBuffer, "OK");

今回の修正で,スレッドIDが-1でHgが発行されることは無くなると思われるのだが,上のほうで書いたように,Interface誌によれば,スレッドIDが-1でHgコマンドが発行された場合はすべてのスレッドを指す(?)らしい(?)ので,まあ未対応として「$#00」を返すようにする.まあたぶん不要だとは思うのだけど,いちおう入れておく.

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

(gdb) info threads
7 Thread 135026496 ( Name: httpd, State: SLP, Priority: 09) 0x0805eeab in sigprocmask ()
6 Thread 135025728 ( Name: telnetd, State: SLP, Priority: 08) 0x0805eeab in sigprocmask ()
5 Thread 135024960 ( Name: clock, State: SLP, Priority: 07) 0x0805eeab in sigprocmask ()
4 Thread 135024192 ( Name: idle, State: RUN, Priority: 1f) 0x0806094f in tcflow ()
3 Thread 135023424 ( Name: outlog, State: SLP, Priority: 03) 0x0805eeab in sigprocmask ()
2 Thread 135021888 ( Name: extintr, State: SLP, Priority: 01) 0x0805eeab in sigprocmask ()
* 1 Thread 135022656 ( Name: stubd, State: RUN, Priority: 02) 0x0804af59 in breakpoint () at i386-stub.c:1184
(gdb) info threads
7 Thread 135026496 ( Name: httpd, State: SLP, Priority: 09) 0x0805eeab in sigprocmask ()
6 Thread 135025728 ( Name: telnetd, State: SLP, Priority: 08) 0x0805eeab in sigprocmask ()
5 Thread 135024960 ( Name: clock, State: SLP, Priority: 07) 0x0805eeab in sigprocmask ()
4 Thread 135024192 ( Name: idle, State: RUN, Priority: 1f) 0x0806094f in tcflow ()
3 Thread 135023424 ( Name: outlog, State: SLP, Priority: 03) 0x0805eeab in sigprocmask ()
2 Thread 135021888 ( Name: extintr, State: SLP, Priority: 01) 0x0805eeab in sigprocmask ()
* 1 Thread 135022656 ( Name: stubd, State: RUN, Priority: 02) 0x0804af59 in breakpoint () at i386-stub.c:1184
(gdb)

おー,スレッドIDが-1のへんなスレッドが無くなっている.問題なさそうだ.

この場合の通信内容は以下.

($T054:58f60e08;5:58f60e08;8:59af0408;thread:080c4840;#7e)[+]
[$M8048088,1:55#c2](+)($OK#9a)[+]
[$T080c4840#1f](+)($OK#9a)[+]
[$qfThreadInfo#bb](+)($#00)[+]
[$qL1200000000000000000#50](+)($qM010000000000000000000000000080c4540#97)[+]
[$qL02000000000080c4540#97](+)($qM01000000000080c454000000000080c4840#e2)[+]
[$qL02000000000080c4840#9a](+)($qM01000000000080c484000000000080c4b40#0f)[+]
[$qL02000000000080c4b40#c4](+)($qM01000000000080c4b4000000000080c4e40#3c)[+]
[$qL02000000000080c4e40#c7](+)($qM01000000000080c4e4000000000080c5140#0c)[+]
[$qL02000000000080c5140#94](+)($qM01000000000080c514000000000080c5440#dc)[+]
[$qL02000000000080c5440#97](+)($qM01000000000080c544000000000080c5740#e2)[+]
[$qL02000000000080c5740#9a](+)($qM00100000000080c5740#9a)[+]
[$qThreadExtraInfo,80c5740#20](+)($#00)[+]
[$qP0000001f00000000080c5740#c3](+)($QP0000001f00000000080c5740000000011000000000080c5740000000020110000000403SLP0000000805httpd000000100209#17)[+]
[$Hg80c5740#4a](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000040570c08fcc5110818c61108ecc7110800000000abee050802020000330000003b0000003b0000003b0000003b0000001b000000#43)[+]
[$qP0000001f00000000080c5440#c0](+)($QP0000001f00000000080c5440000000011000000000080c5440000000020110000000403SLP0000000807telnetd000000100208#de)[+]
[$Hg80c5440#47](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000040540c08fc35110818361108ec37110800000000abee050802020000330000003b0000003b0000003b0000003b0000001b000000#b0)[+]
[$qP0000001f00000000080c5140#bd](+)($QP0000001f00000000080c5140000000011000000000080c5140000000020110000000403SLP0000000805clock000000100207#f1)[+]
[$Hg80c5140#44](+)($OK#9a)[+]
[$g#67](+)($0000000098b00d08ea04000040510c083ca7100858a71008eca7100800000000abee050806020000330000003b0000003b0000003b0000003b0000001b000000#8b)[+]
[$qP0000001f00000000080c4e40#f0](+)($QP0000001f00000000080c4e40000000011000000000080c4e40000000020110000000403RUN0000000804idle00000010021f#1e)[+]
[$Hg80c4e40#77](+)($OK#9a)[+]
[$g#67](+)($0400000004000000ffffffff404e0c088c171008b8171008ec171008000000004f09060813020000330000003b0000003b0000003b0000003b0000001b000000#a4)[+]
[$qP0000001f00000000080c4b40#ed](+)($QP0000001f00000000080c4b40000000011000000000080c4b40000000020110000000403SLP0000000806outlog000000100203#dc)[+]
[$Hg80c4b40#74](+)($OK#9a)[+]
[$g#67](+)($0000000060800f08ea040000404b0c083c870f0858870f08ec870f0800000000abee050806020000330000003b0000003b0000003b0000003b0000001b000000#ac)[+]
[$qP0000001f00000000080c4540#c0](+)($QP0000001f00000000080c4540000000011000000000080c4540000000020110000000403SLP0000000807extintr000000100201#f5)[+]
[$Hg80c4540#47](+)($OK#9a)[+]
[$g#67](+)($0000000006000000ea04000040450c086c660e0888660e08ec670e0800000000abee050806020000330000003b0000003b0000003b0000003b0000001b000000#34)[+]
[$qP0000001f00000000080c4840#c3](+)($QP0000001f00000000080c4840000000011000000000080c4840000000020110000000403RUN0000000805stubd000000100202#14)[+]
[$Hg80c4840#4a](+)($OK#9a)[+]
[$g#67](+)($00880d0800000000ea04000040480c0858f60e0858f60e08ecf70e080000000059af040802020000330000003b0000003b0000003b0000003b0000001b000000#81)[+]
[$T080c5740#1f](+)($OK#9a)[+]
[$T080c5440#1c](+)($OK#9a)[+]
[$T080c5140#19](+)($OK#9a)[+]
[$T080c4e40#4c](+)($OK#9a)[+]
[$T080c4b40#49](+)($OK#9a)[+]
[$T080c4540#1c](+)($OK#9a)[+]
[$T080c4840#1f](+)($OK#9a)[+]
[$qL12000000000080c5740#9b](+)($qM00100000000080c5740#9a)[+]
[$qP0000001f00000000080c5740#c3](+)($QP0000001f00000000080c5740000000011000000000080c5740000000020110000000403SLP0000000805httpd000000100209#17)[+]
[$Hg80c5740#4a](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000040570c08fcc5110818c61108ecc7110800000000abee050802020000330000003b0000003b0000003b0000003b0000001b000000#43)[+]
[$qP0000001f00000000080c5440#c0](+)($QP0000001f00000000080c5440000000011000000000080c5440000000020110000000403SLP0000000807telnetd000000100208#de)[+]
[$Hg80c5440#47](+)($OK#9a)[+]
[$g#67](+)($0000000000000000ea04000040540c08fc35110818361108ec37110800000000abee050802020000330000003b0000003b0000003b0000003b0000001b000000#b0)[+]
[$qP0000001f00000000080c5140#bd](+)($QP0000001f00000000080c5140000000011000000000080c5140000000020110000000403SLP0000000805clock000000100207#f1)[+]
[$Hg80c5140#44](+)($OK#9a)[+]
[$g#67](+)($0000000098b00d08ea04000040510c083ca7100858a71008eca7100800000000abee050806020000330000003b0000003b0000003b0000003b0000001b000000#8b)[+]
[$qP0000001f00000000080c4e40#f0](+)($QP0000001f00000000080c4e40000000011000000000080c4e40000000020110000000403RUN0000000804idle00000010021f#1e)[+]
[$Hg80c4e40#77](+)($OK#9a)[+]
[$g#67](+)($0400000004000000ffffffff404e0c088c171008b8171008ec171008000000004f09060813020000330000003b0000003b0000003b0000003b0000001b000000#a4)[+]
[$qP0000001f00000000080c4b40#ed](+)($QP0000001f00000000080c4b40000000011000000000080c4b40000000020110000000403SLP0000000806outlog000000100203#dc)[+]
[$Hg80c4b40#74](+)($OK#9a)[+]
[$g#67](+)($0000000060800f08ea040000404b0c083c870f0858870f08ec870f0800000000abee050806020000330000003b0000003b0000003b0000003b0000001b000000#ac)[+]
[$qP0000001f00000000080c4540#c0](+)($QP0000001f00000000080c4540000000011000000000080c4540000000020110000000403SLP0000000807extintr000000100201#f5)[+]
[$Hg80c4540#47](+)($OK#9a)[+]
[$g#67](+)($0000000006000000ea04000040450c086c660e0888660e08ec670e0800000000abee050806020000330000003b0000003b0000003b0000003b0000001b000000#34)[+]
[$qP0000001f00000000080c4840#c3](+)($QP0000001f00000000080c4840000000011000000000080c4840000000020110000000403RUN0000000805stubd000000100202#14)[+]
[$Hg80c4840#4a](+)($OK#9a)[+]
[$g#67](+)($00880d0800000000ea04000040480c0858f60e0858f60e08ecf70e080000000059af040802020000330000003b0000003b0000003b0000003b0000001b000000#81)[+]
(この状態で停止)

qLコマンドの最後が

[$qL02000000000080c5740#9a](+)($qM00100000000080c5740#9a)[+]

となっていて,スレッド数がゼロ,doneフラグが立ったものが返っている.あとよく見るとわかるのだけど,前回まではあった「Hg-1」が今度は無くなっている.またスレッドIDを0xffffffff で qP コマンドが発行されることも無くなっている.うん,問題無しだ.
AD
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

前回までで info threads によるスレッド情報表示ができるようになった.次は thread コマンドによるスレッド切替えだ.ついでに前回問題になった,info threads を2回行うと固まるというバグを修正したい.

まずgdb上で info threads を行うと

(gdb) info threads
7 Thread 135026304 ( Name: httpd, State: SLP, Priority: 09) 0x0805edeb in kill ()
6 Thread 135025536 ( Name: telnetd, State: SLP, Priority: 08) 0x0805edeb in kill
()
5 Thread 135024768 ( Name: clock, State: SLP, Priority: 07) 0x0805edeb in kill ()
4 Thread 135024000 ( Name: idle, State: RUN, Priority: 1f) 0x0806088f in select ()
3 Thread 135023232 ( Name: outlog, State: SLP, Priority: 03) 0x0805edeb in kill ()
* 2 Thread 135022464 ( Name: stubd, State: RUN, Priority: 02) breakpoint ()
at i386-stub.c:1158
1 Thread 135021696 ( Name: extintr, State: SLP, Priority: 01) 0x0805edeb in kill
()
(gdb)

のようにしてスレッド情報が表示されるが,このとき一番左に表示されている1~7の数値が,スレッド番号になる.gdb上でスレッド操作する場合には,この番号によってスレッドを指定する.

たとえばここで

(gdb) thread 3

とかを行うと,カレントスレッドをスレッド番号3である outlog スレッドに切替える.で,where とかやると,そのスレッドのスタックの状態が見れることになる.

で,前回の実装でためしに thread コマンドによるスレッド切替えを実行してみたら,以下のようになった.

(gdb) thread 3
Thread ID 3 has terminated.

(gdb)

このときgdbとスタブの間では,以下の通信が行われている.

[$T080c4a80#4c](+)($#00)[+]

Tコマンドというのがgdbから送られてきているが,現状のスタブでは実装されていないので「$#00」を返している.で,スレッド切替えに失敗しているようだ.ということで,Tコマンドに対して応答するように修正すればいいようだ.

で,いつもどおりremote.cを調べてみる.「'T'」で検索してもそれっぽい部分が見つからなかったので,「"T」で検索したら以下の部分が見つかった.

static int
remote_thread_alive (ptid_t ptid)
{
struct remote_state *rs = get_remote_state ();
int tid = PIDGET (ptid);

if (tid < 0)
xsnprintf (rs->buf, get_remote_packet_size (), "T-%08x", -tid);
else
xsnprintf (rs->buf, get_remote_packet_size (), "T%08x", tid);
putpkt (rs->buf);
getpkt (&rs->buf, &rs->buf_size, 0);
return (rs->buf[0] == 'O' && rs->buf[1] == 'K');
}

関数名から察するに,スレッドの存在を調べているようだ.見たところ,スレッドが存在する場合には"OK"を返せばいいようなのでそーいうふうに実装してみよう.

remote.c を読んだ感じだと,スレッドの存在/消去は適当にremote_thread_alive() が呼ばれることで検出されるようだ.消滅したスレッドは「T」コマンドでOK以外が返ることになり,その場合にはスレッドは消滅したと判断して削除する,という処理になっているようだ.

さらに今回,カレントスレッドの切替えを追っていて気がついたのだが,シグナル発生時のスタブ側のシグナル送信処理(handle_exception()の先頭付近)で,Tコマンドと一緒にスレッド番号を送信することで,カレントスレッドを gdb 側に伝えることができるようだ(さらに新規スレッドの場合には,[New スレッドID]のように表示される).ここで言うTコマンドと言うのは,上で説明したgdb側からスレッドの存在確認のためにgdbから送られてくるTコマンドではなく,ブレーク時などにスタブ側から送信されるものだ(第16回参照).今回はTコマンドがgdb→スタブのものとスタブ→gdbのものの2種類が登場するので,混同しないように注意してほしい.

gdb側がウエイト状態(スタブのブレーク待ちで,gdbプロンプト未表示状態)では,remote.c の remote_wait() によってブレーク待ちとなっている.で,スタブからTコマンドが送られてくるとブレークが発生したと判断してgdbの処理が始まるのだが,ここで

if (strncmp (p, "thread", p1 - p) == 0)
{
p_temp = unpack_varlen_hex (++p1, &thread_num);
record_currthread (thread_num);
p = p_temp;
}

のようにして,"thread" という文字列を見ている部分がある."thread" に続いてスレッドIDを送ることで,カレントスレッドを教えることができるようなのだ.

さらにこの際,record_currthread() という関数が呼ばれている.record_currthread() は以下のようになっている.

static void
record_currthread (int currthread)
{
general_thread = currthread;

/* If this is a new thread, add it to GDB's thread list.
If we leave it up to WFI to do this, bad things will happen. */
if (!in_thread_list (pid_to_ptid (currthread)))
{
add_thread (pid_to_ptid (currthread));
ui_out_text (uiout, "[New ");
ui_out_text (uiout, target_pid_to_str (pid_to_ptid (currthread)));
ui_out_text (uiout, "]\n");
}
}

パッと見た感じだと,カレントスレッドを切替えて,さらにそれが新しいスレッド(既存のスレッドのリスト中に存在しない)ならば,[New ...]というメッセージを表示しているようだ.

なので,今回はスタブのブレーク時のTコマンド送信部分に,スレッドIDを格納するような対処も行う.

で,実装したのがこんな感じ.前回からの差分については diff.txt 参照.

まず,

diff -ruN kozos21/i386-stub.c kozos22/i386-stub.c
--- kozos21/i386-stub.c Sat Nov 24 11:35:14 2007
+++ kozos22/i386-stub.c Sat Nov 24 14:39:50 2007
@@ -831,6 +831,11 @@
ptr = mem2hex((char *)®isters[PC], ptr, 4, 0); /* PC */
*ptr++ = ';';

+ strcpy(ptr, "thread:");
+ ptr += 7;
+ ptr = intNToHex(ptr, (int)gen_thread->id, 4);
+ *ptr++ = ';';
+
*ptr = '\0';

putpacket (remcomOutBuffer);

という部分で,スタブのブレーク時にTコマンドを発行する際に,レジスタ情報をいくつかパラメータとして追加した後,「thread:(スレッドID);」のようなパラメータを追加している.これによりgdb側でカレントスレッドを認識できる.これはスタブからgdbへのTコマンドだ.

さらに,こちらはgdbからスタブに対するTコマンド

#endif
break;

+ case 'T':
+ {
+ int threadid;
+ kz_thread *thp;
+ hexToInt(&ptr, &threadid);
+ thp = (kz_thread *)threadid;
+ if (thp->id) {
+ strcpy (remcomOutBuffer, "OK");
+ } else {
+ strcpy (remcomOutBuffer, "E01");
+ }
+ }
+ break;
+
case 'q':
switch (*ptr++)
{

スタブ側はgdbからTコマンドが送られてきた場合には,そのスレッドの存在の有無を調べてOK or エラーを返している.remote.c を読む限り,エラーは何を返してもよさそうだが,とりあえず E01 を返している.

次に「qP」コマンドに対する修正.前回,スレッドIDが 0xffffffff でqPコマンドが送られてくることの対処だ.

@@ -1029,7 +1048,16 @@
mode = hexToIntN(&ptr, 4);
threadid[0] = hexToIntN(&ptr, 4);
threadid[1] = hexToIntN(&ptr, 4);
- thp = (kz_thread *)threadid[1];
+ if (threadid[1] == 0xffffffff)
+ {
+ /*
+ * 何を返すべきかちょっと不明なので,とりあえず
+ * カレントスレッドを返す.
+ */
+ thp = gen_thread; /* current を返すべきか? 不明... */
+ } else {
+ thp = (kz_thread *)threadid[1];
+ }

ptr = remcomOutBuffer;
*ptr++ = 'Q';
@@ -1041,13 +1069,13 @@
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);
+ ptr = intNToHex(ptr, 0, 4);
+ ptr = intNToHex(ptr, (int)thp->id, 4);
}
if (mode & TAG_EXISTS) {
ptr = intNToHex(ptr, TAG_EXISTS, 4); /* mode */
ptr = intNToHex(ptr, 1, 1); /* length */
- *ptr++ = '1';
+ *ptr++ = thp->id ? '1' : '0';
}
if (mode & TAG_DISPLAY) {
ptr = intNToHex(ptr, TAG_DISPLAY, 4); /* mode */

実は gdb のソースをちょっと追いかけたのだが,どんなときにスレッドIDが0xffffffff として qP が発行されるのかが,ちょっとわからなかった.まあほんとはそんなあまっちょろいこと言ってないでちゃんと調べなければならないのだが,とりあえずカレントスレッドを返してみる.

注意しなければならないのは,「qP」コマンドの応答時には,remote_unpack_thread_info_response()でスレッドIDのチェックをしているので,スレッドIDは送られてきた 0xffffffff をそのまま返して,TAG_THREADID によってカレントスレッドのスレッドIDを返しているという点だ.あとついでに TAG_EXISTS によってスレッドの存在の有無を返す際に,スレッドの存在をちゃんと調べるように修正.

最後に,Hgコマンドに対する修正.

@@ -1098,6 +1126,7 @@
{
if (rev) val = -val;
stub_restore_regs(gen_thread);
+ if (val == -1) val = (int)current;
gen_thread = (kz_thread *)val;
stub_store_regs(gen_thread);
strcpy (remcomOutBuffer, "OK");

実は Hg コマンドによるスレッド切替えがすでに実装されているため,gdb での thread コマンドによるスレッド切替え動作は,スタブ側で今回新規に実装する必要は無い.ただ,Hg に関しても

Hg-1

のようにしてスレッドIDが 0xffffffff で送られてくる場合があるようなので,これもとりあえずカレントスレッドにするように対処してみた.

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

画像はこちら

[New Thread 135022688] と表示されていて,新しいスレッドが作成されたことが検知できていることに注目.

info threads を実行してみる.

画像はこちら

無事に表示されている.もう一度 info threads を実行してみよう.

画像はこちら

おー,2回繰り返しても問題無くなった.

次に,thread コマンドによってスレッドを切替えてみる.

画像はこちら

で,where によりスタックトレースをとってみよう.

画像はこちら

ふむ,outlog_main() から呼び出されているので,たしかに outlog スレッドのトレースのようだ.こんなふうにして,ブレーク時の各スレッドの情報を見ることができる.当然だけど up コマンドでスタックトレースを追いかければ,関数呼び出しの階層を追うことができる.ソースコードももちろんその都度表示される.

ちなみにブレークポイントもスレッド単位で設定できて,たとえば

(gdb) break func thread 3

みたいに設定すれば,そのブレークポイントはスレッド番号3のスレッドのときのみ,反応する.これはどんなふうに実現されているかというと,
  • 通常通り,ブレークポイントを設定する.
  • ブレーク時にgdbはカレントスレッドを聞きにいき,当該のスレッドならばブレークする.
  • 当該のスレッドでないならば,そのブレークは無視して,動作続行する.
というような処理が行われる.まあ実際には指定されたスレッドでない場合にも,CPUはトラップ命令を実行してブレークが発生しているのだけど,gdbがうまく無視して先に進めてくれているわけだ.このへんの処理は,gdbとスタブの通信内容を見てみるとgdbが実によろしくやってくれていることがわかり,非常に面白い.

ちょっと試してみよう.まず,ブレークポイントの設定.

(gdb) break dummy_func thread 3
Breakpoint 1 at 0x804b11e: file telnetd.c, line 21.
(gdb)

第16回で説明したことでちょっと忘れているかもしれないけど,telnet で接続して call を実行することで,dummy_func() というダミー関数が呼ばれるようになっている.で,dummy_func() に対してスレッド番号3(outlogスレッド)でブレークポイントを張ってみた.

実際に telnet から call を実行してみよう.

% telnet 192.168.0.3 20001
Trying 192.168.0.3...
Connected to 192.168.0.3.
Escape character is '^]'.
> call
OK
>

ブレークポイントが張ってある dummy_func() が呼ばれたにもかかわらず,ブレークせずに正常終了している.このときのgdbとスタブの通信は以下.

...
Sat Nov 24 14:08:43 2007
Sat Nov 24 14:08:44 2007
Sat Nov 24 14:08:45 2007
($T054:ec561208;5:f4561208;8:1fb10408;thread:080c4260;#31)[+]
[$g#67](+)($00000000477a0a080000000060420c08ec561208f4561208ec571208000000001fb1040812020000330000003b0000003b0000003b0000003b0000001b000000#98)[+]
[$P8=1eb10408#ba](+)($OK#9a)[+]
[$M8048088,1:55#c2](+)($OK#9a)[+]
[$M804b11e,1:c7#43](+)($OK#9a)[+]
[$Hc80c4260#42](+)($#00)[+]
[$s#73](+)($T054:ec561208;5:f4561208;8:25b10408;thread:080c4260;#01)[+]
[$m8048088,1#3e](+)($55#6a)[+]
[$M8048088,1:cc#1e](+)($OK#9a)[+]
[$m804b11e,1#8f](+)($c7#9a)[+]
[$M804b11e,1:cc#6f](+)($OK#9a)[+]
[$Hc0#db](+)($#00)[+]
[$c#63](+)Sat Nov 24 14:08:47 2007
Sat Nov 24 14:08:48 2007
Sat Nov 24 14:08:49 2007
Sat Nov 24 14:08:50 2007
...

まず最初に T コマンドが発行されているので,ブレークしているのは確かだ.ただその後,以下の動作が行われているようだ.
  • Mコマンドによりブレークポイントを元に戻す.
  • sコマンドによりステップ実行で処理をちょい先に進める.
  • Mコマンドにより再度ブレークポイントを設定する.
  • cコマンドにより continue する.
つまり,ブレークポイントを無視して continue しているのである.

ステップ実行で処理を進めるのは,continue の直後に同じ場所でまたブレークしないようにするためのおなじみの動作だ.このへんについては第16回を参照.

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

えー,こないだ本屋で初めて見たのだけど,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 ってどういう意味なのだろう?カレントスレッドの情報を返せばいいような気もするが...まあこのへんはまた次回考えよう.
AD
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

今回も前々回,前回に引続き,スレッド対応を進めよう.とりあえずコマンドが足りないことが明らかにわかっているので,それらを追加してみる.

まずは前回の通信結果を見てみよう.

[$qfThreadInfo#bb](+)($#00)[+]
[$qL1200000000000000000#50](+)($qM010000000000000000000000000080c3360#96)[+]
[$qL02000000000080c3360#96](+)($qM01000000000080c336000000000080c3660#e0)[+]
[$qL02000000000080c3660#99](+)($qM01000000000080c366000000000080c3960#e6)[+]
[$qL02000000000080c3960#9c](+)($qM01000000000080c396000000000080c3c60#13)[+]
[$qL02000000000080c3c60#c6](+)($qM01000000000080c3c6000000000080c3f60#40)[+]
[$qL02000000000080c3f60#c9](+)($qM01000000000080c3f6000000000080c4260#10)[+]
[$qL02000000000080c4260#96](+)($qM01000000000080c426000000000080c4560#e0)[+]
[$qL02000000000080c4560#99](+)($qM01100000000080c4560#9a)[+]
[$qC#b4](+)($#00)[+]
[$qThreadExtraInfo,80c4560#1f](+)($#00)[+]
[$qP0000001f00000000080c4560#c2](+)($#00)[+]
[$Hg80c4560#49](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c4260#bf](+)($#00)[+]
[$Hg80c4260#46](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3f60#f2](+)($#00)[+]
[$Hg80c3f60#79](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3c60#ef](+)($#00)[+]
[$Hg80c3c60#76](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3960#c5](+)($#00)[+]
[$Hg80c3960#4c](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3660#c2](+)($#00)[+]
[$Hg80c3660#49](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3360#bf](+)($#00)[+]
[$Hg80c3360#46](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$Hg0#df](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
(この状態で停止)

で,スタブ側で応答できていないコマンドをリストアップすると,以下になる.
  • qfThreadInfo
  • qC
  • qThreadExtraInfo,80c4560
  • qP0000001f00000000080c4560
  • Hg80c4560
これらはどれもスタブ側では対応されていないため,$#00を返している.で,なにを返さなければいけないかというと,資料もサンプルも無いので,いつもどおりremote.cを読んで調べるしかない.

まず手始めに qfThreadInfo だが,これは前々回に説明したように,実装の必要は無い.

次に qC だが,これは

...
[$qL02000000000080c4260#96](+)($qM01000000000080c426000000000080c4560#e0)[+]
[$qL02000000000080c4560#99](+)($qM01100000000080c4560#9a)[+]
[$qC#b4](+)($#00)[+]
...

のようにして,qLコマンドによるスレッド一覧取得のあとに発行されている.ということは,remote_threads_info()からの一連のスレッド情報取得処理のあとに,qCコマンドを発行する部分があるのではなかろうか?

ほんとはこーいうときこそ,デバッガのステップ実行で処理の流れを見ていくのが便利なのだが,まあソースコードをよく読んでみると,remote_threads_info() の最後で remote_find_new_threads() という関数を呼んでおり,さらにそこから呼んでいる remote_current_thread() という関数の内部で,qC コマンドを発行しているようだ.(ていうかそれよりもまずはためしに「qC」で検索かけるべきだったね.そしたら一撃で見つかった)

static ptid_t
remote_current_thread (ptid_t oldpid)
{
struct remote_state *rs = get_remote_state ();

putpkt ("qC");
getpkt (&rs->buf, &rs->buf_size, 0);
if (rs->buf[0] == 'Q' && rs->buf[1] == 'C')
/* Use strtoul here, so we'll correctly parse values whose highest
bit is set. The protocol carries them as a simple series of
hex digits; in the absence of a sign, strtol will see such
values as positive numbers out of range for signed 'long', and
return LONG_MAX to indicate an overflow. */
return pid_to_ptid (strtoul (&rs->buf[2], NULL, 16));
else
return oldpid;
}

qCコマンドの発行時には,応答として QC コマンドというのが返るようだ.で,引数になんか整数がつくらしい.まあ関数の名前が remote_current_thread() なので,カレントスレッドのスレッドIDを返せばいいように思える.

ここで返したスレッドIDは,呼び出し元の remote_find_new_threads() で

static void
remote_find_new_threads (void)
{
remote_threadlist_iterator (remote_newthread_step, 0,
CRAZY_MAX_THREADS);
if (PIDGET (inferior_ptid) == MAGIC_NULL_PID) /* ack ack ack */
inferior_ptid = remote_current_thread (inferior_ptid);
}

のようにして,inferior_ptid という変数に代入される.で,remote_fetch_registers() で

static void
remote_fetch_registers (struct regcache *regcache, int regnum)
{
struct remote_state *rs = get_remote_state ();
struct remote_arch_state *rsa = get_remote_arch_state ();
int i;

set_thread (PIDGET (inferior_ptid), 1);
...

のようにして,set_thread() により general_thread という変数に設定される.set_thread() の内部では general_thread と continue_thread という2種類の変数を設定しており,どうも gdb は General Thread と Continue Thread という2種類のカレントスレッドを持っているように思える.想像だが,General Thread は現在デバッグ対象となっているスレッド,Continue Thread は continue 実行時に動作再開すべきスレッド?(つまり,現在ブレークしているスレッド?)のような気がする...のだが,詳細未調査.

まあとりえあずは,qCにはカレントスレッドを返せばいいように思えるのでそうする.

で,次は qThreadExtraInfo,80c4560 というコマンドだ.これはremote_threads_extra_info() という関数で発行している(「ThreadExtraInfo」で検索したら一撃で見つかった).で,何を期待しているのか remote_threads_extra_info() を見てみたのだが,どうもスレッドの付加情報(名前とか優先度とか)を取得するためのコマンドらしく,とりあえずは無くても動くみたいだ.なので今回は未実装とする.

次は qP0000001f00000000080c4560 というコマンドだ.これは「qP」で検索したが...出てこない...「'P'」で検索したら,以下が出てきた.

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;
}

引数として整数値をひとつ,スレッドIDをひとつ送ってくるようだ.で,この pack_threadinfo_request() の呼び出しもとなのだけど,remote_get_threadinfo()を経由して2箇所から呼ばれている.ひとつは,先程 qThreadExtraInfo コマンドを呼び出していたremote_threads_extra_info() の内部で,qThreadExtraInfo に失敗した際に

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);
...

のようにして remote_get_threadinfo() を呼び出している.応答を受信した際には,スレッド名とかの設定が行われるように見える.

もう1箇所は,get_and_display_threadinfo()という関数から呼ばれている.

int
get_and_display_threadinfo (threadref *ref)
{
int result;
int set;
struct gdb_ext_thread_info threadinfo;

set = TAG_THREADID | TAG_EXISTS | TAG_THREADNAME
| TAG_MOREDISPLAY | TAG_DISPLAY;
if (0 != (result = remote_get_threadinfo (ref, set, &threadinfo)))
display_thread_info (&threadinfo);
return result;
}

で,qPを発行するタイミングなのだけど,前者のほうでは qThreadExtraInfo 失敗時にqP コマンドが発行されているので,どうも qfThreadInfo に対する qL コマンドのように
  • qThreadExtraInfo ... 先進的で高効率な,スレッド情報取得方法
  • qP ... レガシーな,スレッド情報取得方法
という関係にあるのではなかろうか.gdb側ではまずは qThreadExtraInfo を試してみて,ダメならば qP を試してみるわけだ.まあ実際のところ remote_threads_extra_info() の内部ではスレッド名などの設定をしているので,関数名の通り,スレッドの拡張情報取得コマンドだと思われる.なので qThreadExtraInfo と同様,とりあえずは未実装でいいだろう.

次は Hg80c4560 だ.これは「'H'」で検索したら,以下のような部分があった.

static void
set_thread (int th, int gen)
{
struct remote_state *rs = get_remote_state ();
char *buf = rs->buf;
int state = gen ? general_thread : continue_thread;

if (state == th)
return;

buf[0] = 'H';
buf[1] = gen ? 'g' : 'c';
if (th == MAGIC_NULL_PID)
{
buf[2] = '0';
buf[3] = '\0';
}
else if (th < 0)
xsnprintf (&buf[2], get_remote_packet_size () - 2, "-%x", -th);
else
xsnprintf (&buf[2], get_remote_packet_size () - 2, "%x", th);
putpkt (buf);
getpkt (&rs->buf, &rs->buf_size, 0);
if (gen)
general_thread = th;
else
continue_thread = th;
}

引数としてはスレッドIDを渡してくるようだ.応答はとくに見ていないので,スタブに対するなんらかの指示をしているように思われる.スタブ側に送ったスレッドIDを general_thread (もしくは continue_thread)に設定しているので,カレントスレッドの切替えの指示だと思われる...と,ここまで書いて思い出したけど,考えてみれば前々回の最後で,Hgはカレントスレッドの切替えだろうと書いていたね...ということで,スレッドIDで指定されたスレッドに,カレントスレッドを切替えればいいわけだ.

で,書いたのがこんな感じ.差分は以下.今回も i386-stub.c のみの修正.

diff -ruN kozos19/i386-stub.c kozos20/i386-stub.c
--- kozos19/i386-stub.c Mon Nov 19 23:37:48 2007
+++ kozos20/i386-stub.c Tue Nov 20 00:49:16 2007
@@ -92,6 +92,7 @@
#include
#include
#include "thread.h"
+#include "stublib.h"

/************************************************************************
*
@@ -1006,10 +1007,45 @@
*ptr++ = '\0';
}
break;
+ case 'C':
+ {
+ ptr = remcomOutBuffer;
+ *ptr++ = 'Q';
+ *ptr++ = 'C';
+ ptr = intNToHex(ptr, (int)gen_thread->id, 4);
+ }
+ break;
default:
break;
}
break;
+
+ case 'H':
+ switch (*ptr++)
+ {
+ case 'g':
+ {
+ int val, rev = 0;
+ if (*ptr == '-')
+ {
+ rev++;
+ ptr++;
+ }
+ if (hexToInt(&ptr, &val))
+ {
+ if (rev) val = -val;
+ stub_restore_regs(gen_thread);
+ gen_thread = (kz_thread *)val;
+ stub_store_regs(gen_thread);
+ strcpy (remcomOutBuffer, "OK");
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
} /* switch */

/* reply to the request */

まあすでに説明してしまったが,qC コマンドと Hg コマンドを実装してある.qC はカレントスレッドを返し,Hg はカレントスレッドの切替えを行っている.注意として set_thread() の内部ではスレッドIDにマイナスを付加して送信する場合があるので,Hg受信時には '-' の有無を見ている.スレッドの切替えは,stublib.c で提供されているstub_restore_regs(), stub_store_regs() を利用している.stub_store_regs() を呼び出すと,レジスタの値を格納している配列 registers[] が指定されたスレッドのものに書きかわる.そして g コマンド受信時には,配列 registers[] が参照されてレジスタ値をgdbに送信するので,gdb側ではカレントスレッドのレジスタ値を受け取ることになる.

ちなみに gen_thread は stublib.c で定義されている外部変数で,スタブのカレントスレッドである.(thread.cが持っているカレントスレッド(変数current)ではないので注意.current はKOZOSのカレントスレッドだが,gen_thread はスタブで現在デバッグ対象になっているスレッドである)

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

画像はこちら

おー,こんどはなんかスレッドごとにそれっぽい情報が表示されている.ちなみに左端に * がついているのが,現在デバッグ対象となっているカレントスレッドだ.breakpoint() で停止していうので,おそらくこれがstubd だろう(Ctrl-C受信により,kz_break()を呼び出して停止している).

他のスレッドは,だいたい kill() で止まっているようだ.これはシステムコール呼び出しにより,kz_syscall()内の kill(..., SIGSYS) で停止しているからだ.たぶん kz_recv() によるメッセージ受信待ちになっているのだと思われる.ひとつだけ select() で停止しているスレッドがいるが,これは idle スレッドだ.

info threads 実行時の通信内容は以下になる.

[$qfThreadInfo#bb](+)($#00)[+]
[$qL1200000000000000000#50](+)($qM010000000000000000000000000080c3360#96)[+]
[$qL02000000000080c3360#96](+)($qM01000000000080c336000000000080c3660#e0)[+]
[$qL02000000000080c3660#99](+)($qM01000000000080c366000000000080c3960#e6)[+]
[$qL02000000000080c3960#9c](+)($qM01000000000080c396000000000080c3c60#13)[+]
[$qL02000000000080c3c60#c6](+)($qM01000000000080c3c6000000000080c3f60#40)[+]
[$qL02000000000080c3f60#c9](+)($qM01000000000080c3f6000000000080c4260#10)[+]
[$qL02000000000080c4260#96](+)($qM01000000000080c426000000000080c4560#e0)[+]
[$qL02000000000080c4560#99](+)($qM01100000000080c4560#9a)[+]
[$qThreadExtraInfo,80c4560#1f](+)($#00)[+]
[$qP0000001f00000000080c4560#c2](+)($#00)[+]
[$Hg80c4560#49](+)($OK#9a)[+]
[$g#67](+)($0000000000000000e203000060450c08fcb5110818b61108ecb711080000000067eb050802020000330000003b0000003b0000003b0000003b0000001b000000#b6)[+]
[$qP0000001f00000000080c4260#bf](+)($#00)[+]
[$Hg80c4260#46](+)($OK#9a)[+]
[$g#67](+)($0000000000000000e203000060420c08fc25110818261108ec2711080000000067eb050802020000330000003b0000003b0000003b0000003b0000001b000000#23)[+]
[$qP0000001f00000000080c3f60#f2](+)($#00)[+]
[$Hg80c3f60#79](+)($OK#9a)[+]
[$g#67](+)($0000000098a00d08e2030000603f0c083c97100858971008ec9710080000000067eb050806020000330000003b0000003b0000003b0000003b0000001b000000#be)[+]
[$qP0000001f00000000080c3c60#ef](+)($#00)[+]
[$Hg80c3c60#76](+)($OK#9a)[+]
[$g#67](+)($0400000004000000ffffffff603c0c088c071008b8071008ec071008000000000b06060813020000330000003b0000003b0000003b0000003b0000001b000000#95)[+]
[$qP0000001f00000000080c3960#c5](+)($#00)[+]
[$Hg80c3960#4c](+)($OK#9a)[+]
[$g#67](+)($0000000060700f08e203000060390c083c770f0858770f08ec770f080000000067eb050806020000330000003b0000003b0000003b0000003b0000001b000000#f7)[+]
[$qP0000001f00000000080c3660#c2](+)($#00)[+]
[$Hg80c3660#49](+)($OK#9a)[+]
[$g#67](+)($00780d0800000000e203000060360c0858e60e0858e60e08ece70e080000000015ac040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3360#bf](+)($#00)[+]
[$Hg80c3360#46](+)($OK#9a)[+]
[$g#67](+)($0000000006000000e203000060330c086c560e0888560e08ec570e080000000067eb050806020000330000003b0000003b0000003b0000003b0000001b000000#a7)[+]
[$Hg80c3660#49](+)($OK#9a)[+]
[$g#67](+)($00780d0800000000e203000060360c0858e60e0858e60e08ece70e080000000015ac040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
(この状態で停止)

まあもうさんざん説明しているので細かい説明は省くが,HgコマンドとqCコマンドが応答している...と思ったのだが,よく見るとqCが来ていないね.これは実は,info threads 実行前にすでにqCが発行されていて,それが成功しているので,改めて発行されていないということのようだ.(実際に確認してみたら,info threads 実行の前に qC があって,ちゃんと値が返っていた.このように,新しいコマンドが実装されると,コマンドの呼び出しシーケンスが微妙に変わったりするので注意)

とりあえず,ちゃんと動いた.ただ,せめてスレッド名くらいは出てくれないと,情報としてちょっと寂しいよね.まあこのへんはまた次回考えよう.
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

今回はスタブのエンディアンについて考えよう.

まずエンディアンについてだけど,前回の結果では,スレッドIDのエンディアンがどうもひっくり返っているらしいという問題があった.

エンディアンに対する考え方は,3種類あるとおもう.
  1. ターゲット機種に依存したエンディアンにする.(たとえばターゲット機種がi386ならばリトルエンディアンで通信するが,MIPSとかならビッグエンディアンで通信する)
  2. ホストPCの機種に依存したエンディアンにする.
  3. 通信はビッグエンディアンかリトルエンディアンのどちらかに固定する.
まず1だが,スタブ側としては一番実装しやすい.というのは,あまりエンディアンのことを考える必要が無くなるからだ.しかしgdb側では,ターゲット機種に応じた動作をする必要がある.

次に2だが,これは問題だ.スタブ側ではホストPCの機種など(gdb側から通知しない限りは)知るよしもない.なのにホストPC上のgdbが自分のエンディアンで動いてしまうと,たとえばAT互換機上の Linux で動かす場合にはリトルエンディアンだが,PowerMAC上の Linux で動かす場合にはビッグエンディアン,ということになってしまう.それに応じてスタブ側ではうまくエンディアンを切替えるか,もしくはホストPCをAT互換機とかに固定してしまうか,なんというかいまいちな動作になってしまう.

次に3だが,ターゲット依存もなくなるので,gdb側としてはこれがいちばんうれしい.通常ネットワーク通信はビッグエンディアンなので,ビッグエンディアンに固定するのがいいように思う.

で,実際にgdbがどのように動作しているのかなのだが,まあエンディアンについては前回紹介したremote.cを良く読むか,スタブのサンプル(場合によっては他CPUの)を見て,コマンドごとに判断するしか無い.たとえば remote.c では,整数値のデコーディングは以下のようになっている.

static int
stub_unpack_int (char *buff, int fieldlength)
{
int nibble;
int retval = 0;

while (fieldlength)
{
nibble = stubhex (*buff++);
retval |= nibble;
fieldlength--;
if (fieldlength)
retval = retval << 4;
}
return retval;
}

これは,先頭の桁を4ビットずつシフトしながら加算しているので,ホストPCのエンディアンに依存せず,必ずビッグエンディアンとして値を読む.

しかしスレッドIDのデコーディングは以下のようになっている.

static char *
unpack_threadid (char *inbuf, threadref *id)
{
char *altref;
char *limit = inbuf + BUF_THREAD_ID_SIZE;
int x, y;

altref = (char *) id;

while (inbuf < limit)
{
x = stubhex (*inbuf++);
y = stubhex (*inbuf++);
*altref++ = (x << 4) | y;
}
return inbuf;
}

これは,引数idで渡されたアドレスに先頭桁から順次格納していくので,ホストPCのエンディアン依存になる.げげっ!

...ように思えるのだが,よく読むと実はこのあとに呼ばれるremote_newthread_step() の内部の処理で threadref_to_int() を通すことで,スレッドIDをビッグエンディアンとして読み直している.あー,あせった.ちなみに threadref_to_int() は以下.

static int
threadref_to_int (threadref *ref)
{
int i, value = 0;
unsigned char *scan;

scan = *ref;
scan += 4;
i = 4;
while (i-- > 0)
value = (value << 8) | ((*scan++) & 0xff);
return value;
}

つまり,remote.c 内部では整数値はビッグエンディアンとして読み込んでいるのだが,スレッドIDに関しては,とりあえず送られてきたままのバイト列をスレッドIDとしてそのまま格納し,あとで threadref_to_int() によってビッグエンディアンとして読み,整数値に変換している,ということになる.注意しなければならないのは,qLコマンドによるnext threadID の送信だ.これは整数値への変換前の(remote_threadlist_iterator()内部でcopy_threadref()によってコピーした)バイト列をそのまま送ってくる.このように remote.c 内部では,スレッドIDをバイト列のまま扱っているので注意が必要だ.

まあ結局のところ,スタブ側では整数値もスレッドIDも,ビッグエンディアンとして処理すればいいようだ.ちなみに以下は i386-stub.c のメモリ→通信用文字列への変換関数である.

char *
mem2hex (mem, buf, count, may_fault)
char *mem;
char *buf;
int count;
int may_fault;
{
int i;
unsigned char ch;

if (may_fault)
mem_fault_routine = set_mem_err;
for (i = 0; i < count; i++)
{
ch = get_char (mem++);
if (may_fault && mem_err)
return (buf);
*buf++ = hexchars[ch >> 4];
*buf++ = hexchars[ch % 16];
}
*buf = 0;
if (may_fault)
mem_fault_routine = NULL;
return (buf);
}

まあよく読めばわかるのだけど,メモリ上の値を先頭バイトから16進数の文字列に変換するだけだ.前回の qL コマンド対応では,この mem2hex() を使って通信データを作成していた.なのでターゲット機種(この場合は,実行形式kozが動作しているPC)である i386 のエンディアンになってしまい,リトルエンディアンとしてスレッドIDが送信されてしまうのが前回のエンディアンの問題の原因だ.

ここでちょっと気がつくことがある.i386-stub.c 内部では,g コマンドによるレジスタ値送信や m コマンドによるメモリ値送信にもmem2hex() が使われているのだ.まあもっとも m コマンドの場合には,メモリ上の値をバイト列として返すので,そもそもエンディアンを考えるべきではなく,単に先頭から順に返せばよい.よってmem2hex() で差し支えない.気になるのは g コマンドによるレジスタ値取得なのだが,レジスタはターゲット機種固有のものなので,ターゲット機種のエンディアンで返すべき,という考えなのだろう,多分.(そもそもレジスタが4バイトだという保証も無いわけだし.スレッドIDなどはCPUに依存する値ではないので,ビッグエンディアンに統一すべきだといえる)

ということでスタブ側では,スレッドIDの送受信部分はビッグエンディアン使用に書き換える必要がある.で,修正したのが以下.で,以下が今回の修正ぶんだ.ちなみに今回は,修正はスタブのみ.

diff -ruN kozos18/i386-stub.c kozos19/i386-stub.c
--- kozos18/i386-stub.c Mon Nov 19 18:42:25 2007
+++ kozos19/i386-stub.c Mon Nov 19 21:53:46 2007
@@ -752,6 +752,41 @@
return (numChars);
}

+int
+hexToIntN (char **ptr, int num)
+{
+ int i;
+ int intValue = 0, hexValue;
+
+ for (i = 0; i < num * 2; i++)
+ {
+ hexValue = hex (**ptr);
+ if (hexValue >= 0)
+ intValue = (intValue << 4) | hexValue;
+ else
+ break;
+
+ (*ptr)++;
+ }
+
+ return (intValue);
+}
+
+char *
+intNToHex (char *ptr, int intValue, int num)
+{
+ int hexValue;
+
+ for (; num; num--)
+ {
+ hexValue = (intValue >> ((num - 1) * 8)) & 0xff;
+ *ptr++ = hexchars[hexValue >> 4];
+ *ptr++ = hexchars[hexValue & 0xf];
+ }
+ *ptr = '\0';
+ return (ptr);
+}
+
/*
* This function does all command procesing for interfacing to gdb.
*/
@@ -939,7 +974,8 @@
countmax = hex(*ptr++) << 4;
countmax += hex(*ptr++);

- hex2mem(ptr, threadid, 8, 0);
+ threadid[0] = hexToIntN(&ptr, 4);
+ threadid[1] = hexToIntN(&ptr, 4);

doneflag = 1;
for (i = 0; i < THREAD_NUM; i++) {
@@ -958,12 +994,14 @@
*ptr++ = hexchars[count >> 4];
*ptr++ = hexchars[count & 0xf];
*ptr++ = doneflag ? '1' : '0';
- ptr = mem2hex(threadid, ptr, 8, 0);
+ ptr = intNToHex(ptr, threadid[0], 4);
+ ptr = intNToHex(ptr, threadid[1], 4);

if (!doneflag) {
threadid[0] = 0;
threadid[1] = (int)thp->id;
- ptr = mem2hex(threadid, ptr, 8, 0);
+ ptr = intNToHex(ptr, threadid[0], 4);
+ ptr = intNToHex(ptr, threadid[1], 4);
}
*ptr++ = '\0';
}

まずスタブでの受信時には,16進文字列→整数値への変換が必要だ.これは前回は hex2mem() という関数を使用したが,これを使ったためにスタブ依存のエンディアンで読んでしまっていた.基本として mem2hex() や hex2mem() は,メモリ上の生の値をバイト単位で送受信するためのサービス関数なので,今回のような整数値の送受信などに利用すべきではない.受信には,文字列をビッグエンディアンとして扱う hexToInt() という関数がすでに用意されているので,こちらを利用しよう...と思ったのだが,hexToInt()は16進文字列がそれ以外の文字で終端されていることを期待しているので,今回のスレッドIDのように,8バイトの整数値(int型2個ぶん)が続けて送られてくるような場合には利用できない.なので,任意サイズの固定長を扱えるhexToIntN() という関数を新規に作成し,そちらを使うようにしている.

あーあと言うの忘れていたけど,gdbではスレッドIDは8バイト(64ビット)として管理しているようだ(64ビットCPUへの考慮か?).KOZOSではスレッドIDは4バイトなので,通信の際には64ビットの半分だけを使用して,残り半分はゼロで埋めている.

次に送信だが,残念ながら整数値をビッグエンディアンとして送信するようなサービス関数がスタブ上に用意されていない.なので,hexToIntN() に対してintNToHex() という関数を作成し,利用している.

では,実行してみよう.前回と同様に実行形式を起動,gdbで接続,continue,Ctrl-Cブレークして,info threads を実行してみる.

画像はこちら

スレッド情報として,

1 Thread 135017312 breakpoint () at i386-stub.c:1059

などのように表示されている.この 135017312 という値は,16進数になおすと0x080c3360 となりそれっぽい値になるので,前回とは異なりスレッドIDが正常にやりとりできていることがわかる.

info threads 実行時の通信内容は以下の通り.

[$qfThreadInfo#bb](+)($#00)[+]
[$qL1200000000000000000#50](+)($qM010000000000000000000000000080c3360#96)[+]
[$qL02000000000080c3360#96](+)($qM01000000000080c336000000000080c3660#e0)[+]
[$qL02000000000080c3660#99](+)($qM01000000000080c366000000000080c3960#e6)[+]
[$qL02000000000080c3960#9c](+)($qM01000000000080c396000000000080c3c60#13)[+]
[$qL02000000000080c3c60#c6](+)($qM01000000000080c3c6000000000080c3f60#40)[+]
[$qL02000000000080c3f60#c9](+)($qM01000000000080c3f6000000000080c4260#10)[+]
[$qL02000000000080c4260#96](+)($qM01000000000080c426000000000080c4560#e0)[+]
[$qL02000000000080c4560#99](+)($qM01100000000080c4560#9a)[+]
[$qC#b4](+)($#00)[+]
[$qThreadExtraInfo,80c4560#1f](+)($#00)[+]
[$qP0000001f00000000080c4560#c2](+)($#00)[+]
[$Hg80c4560#49](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c4260#bf](+)($#00)[+]
[$Hg80c4260#46](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3f60#f2](+)($#00)[+]
[$Hg80c3f60#79](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3c60#ef](+)($#00)[+]
[$Hg80c3c60#76](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3960#c5](+)($#00)[+]
[$Hg80c3960#4c](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3660#c2](+)($#00)[+]
[$Hg80c3660#49](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$qP0000001f00000000080c3360#bf](+)($#00)[+]
[$Hg80c3360#46](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
[$Hg0#df](+)($#00)[+]
[$g#67](+)($00780d08000000001504000060360c0858e60e0858e60e08ece70e08000000003dab040802020000330000003b0000003b0000003b0000003b0000001b000000#41)[+]
(この状態で停止)

まあやはり qC とか Hg とかいったコマンドがスタブで処理できていないのだが,このへんはまた次回!