(第20回)GDBのスレッド対応(その3:スレッド一覧を実装)

2007/11/20

あなたは 人目のお客様です.

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

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

[$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)[+]
(この状態で停止)
で,スタブ側で応答できていないコマンドをリストアップすると,以下になる. これらはどれもスタブ側では対応されていないため,$#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 コマンドの ように という関係にあるのではなかろうか.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で指定されたスレッドに, カレントスレッドを切替えればいいわけだ.

で,書いたのがこんな感じ.

(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 
 #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 があって, ちゃんと値が返っていた.このように,新しいコマンドが実装されると, コマンドの呼び出しシーケンスが微妙に変わったりするので注意)

とりあえず,ちゃんと動いた.ただ,せめてスレッド名くらいは出てくれないと, 情報としてちょっと寂しいよね.まあこのへんはまた次回考えよう.


メールは kozos(アットマーク)kozos.jp まで