今回も前々回,前回に引続き,スレッド対応を進めよう. とりあえずコマンドが足りないことが明らかにわかっているので, それらを追加してみる.
まずは前回の通信結果を見てみよう.
[$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 だが,これは
... [$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 コマンドの ように
次は 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で指定されたスレッドに, カレントスレッドを切替えればいいわけだ.
で,書いたのがこんな感じ.
(2009/04/10 ライセンスに関する文書として,KL-01とLICENSEを追加. 詳しくは第43回を参照)
差分は以下.今回も 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まあすでに説明してしまったが,qC コマンドと Hg コマンドを実装してある. qC はカレントスレッドを返し, Hg はカレントスレッドの切替えを行っている. 注意として set_thread() の内部ではスレッドIDにマイナスを付加して送信する 場合があるので,Hg受信時には '-' の有無を見ている. スレッドの切替えは,stublib.c で提供されている stub_restore_regs(), stub_store_regs() を利用している. stub_store_regs() を呼び出すと,レジスタの値を格納している配列 registers[] が 指定されたスレッドのものに書きかわる.そして g コマンド受信時には, 配列 registers[] が参照されてレジスタ値をgdbに送信するので, gdb側ではカレントスレッドのレジスタ値を受け取ることになる.#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 */
ちなみに 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 があって, ちゃんと値が返っていた.このように,新しいコマンドが実装されると, コマンドの呼び出しシーケンスが微妙に変わったりするので注意)
とりあえず,ちゃんと動いた.ただ,せめてスレッド名くらいは出てくれないと, 情報としてちょっと寂しいよね.まあこのへんはまた次回考えよう.