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

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

まずエンディアンについてだけど,前回の結果では,スレッド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 とかいったコマンドがスタブで処理できていないのだが,このへんはまた次回!