(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.
今回も前々回,前回に引続き,スレッド対応を進めよう.とりあえずコマンドが足りないことが明らかにわかっているので,それらを追加してみる.
まずは前回の通信結果を見てみよう.
で,スタブ側で応答できていないコマンドをリストアップすると,以下になる.
まず手始めに qfThreadInfo だが,これは前々回に説明したように,実装の必要は無い.
次に qC だが,これは
のようにして,qLコマンドによるスレッド一覧取得のあとに発行されている.ということは,remote_threads_info()からの一連のスレッド情報取得処理のあとに,qCコマンドを発行する部分があるのではなかろうか?
ほんとはこーいうときこそ,デバッガのステップ実行で処理の流れを見ていくのが便利なのだが,まあソースコードをよく読んでみると,remote_threads_info() の最後で remote_find_new_threads() という関数を呼んでおり,さらにそこから呼んでいる remote_current_thread() という関数の内部で,qC コマンドを発行しているようだ.(ていうかそれよりもまずはためしに「qC」で検索かけるべきだったね.そしたら一撃で見つかった)
qCコマンドの発行時には,応答として QC コマンドというのが返るようだ.で,引数になんか整数がつくらしい.まあ関数の名前が remote_current_thread() なので,カレントスレッドのスレッドIDを返せばいいように思える.
ここで返したスレッドIDは,呼び出し元の remote_find_new_threads() で
のようにして,inferior_ptid という変数に代入される.で,remote_fetch_registers() で
のようにして,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'」で検索したら,以下が出てきた.
引数として整数値をひとつ,スレッドIDをひとつ送ってくるようだ.で,この pack_threadinfo_request() の呼び出しもとなのだけど,remote_get_threadinfo()を経由して2箇所から呼ばれている.ひとつは,先程 qThreadExtraInfo コマンドを呼び出していたremote_threads_extra_info() の内部で,qThreadExtraInfo に失敗した際に
のようにして remote_get_threadinfo() を呼び出している.応答を受信した際には,スレッド名とかの設定が行われるように見える.
もう1箇所は,get_and_display_threadinfo()という関数から呼ばれている.
で,qPを発行するタイミングなのだけど,前者のほうでは qThreadExtraInfo 失敗時にqP コマンドが発行されているので,どうも qfThreadInfo に対する qL コマンドのように
次は Hg80c4560 だ.これは「'H'」で検索したら,以下のような部分があった.
引数としてはスレッドIDを渡してくるようだ.応答はとくに見ていないので,スタブに対するなんらかの指示をしているように思われる.スタブ側に送ったスレッドIDを general_thread (もしくは continue_thread)に設定しているので,カレントスレッドの切替えの指示だと思われる...と,ここまで書いて思い出したけど,考えてみれば前々回の最後で,Hgはカレントスレッドの切替えだろうと書いていたね...ということで,スレッドIDで指定されたスレッドに,カレントスレッドを切替えればいいわけだ.
で,書いたのがこんな感じ.差分は以下.今回も i386-stub.c のみの修正.
まあすでに説明してしまったが,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 実行時の通信内容は以下になる.
まあもうさんざん説明しているので細かい説明は省くが,HgコマンドとqCコマンドが応答している...と思ったのだが,よく見るとqCが来ていないね.これは実は,info threads 実行前にすでにqCが発行されていて,それが成功しているので,改めて発行されていないということのようだ.(実際に確認してみたら,info threads 実行の前に qC があって,ちゃんと値が返っていた.このように,新しいコマンドが実装されると,コマンドの呼び出しシーケンスが微妙に変わったりするので注意)
とりあえず,ちゃんと動いた.ただ,せめてスレッド名くらいは出てくれないと,情報としてちょっと寂しいよね.まあこのへんはまた次回考えよう.
今回も前々回,前回に引続き,スレッド対応を進めよう.とりあえずコマンドが足りないことが明らかにわかっているので,それらを追加してみる.
まずは前回の通信結果を見てみよう.
[$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
まず手始めに 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 ... レガシーな,スレッド情報取得方法
次は 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 があって,ちゃんと値が返っていた.このように,新しいコマンドが実装されると,コマンドの呼び出しシーケンスが微妙に変わったりするので注意)
とりあえず,ちゃんと動いた.ただ,せめてスレッド名くらいは出てくれないと,情報としてちょっと寂しいよね.まあこのへんはまた次回考えよう.