前回まででGDBの環境はほぼ構築したので、xv6のイメージをロードして、デバッグしてみる。
ISS側のロードに必要なものは、
- kernel
- xv6.img
なのだが、kernelは本当にOSが立ち上がるのに必要なカーネル部分が入っている。xv6.imgは、ハードディスクからロードしてくる、OSの本体と、ユーザプログラムの部分だ。 私のISSでは、まずはimgイメージはロードせず、カーネルからIDEモジュールを叩くとimgファイルにアクセスするような仕組みになっている。imgから直接ロードすることはできない。
kernel: $(OBJS) entry.o initcode kernel.ld $(LD) $(LDFLAGS) -T kernel.ld -o kernel entry.o $(OBJS) -b binary initcode $(OBJDUMP) -S kernel > kernel.asm $(OBJDUMP) -D -x kernel > kernel.dmp $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym
このとおり、kernelには初期化とエントリしか入っておらず、最低限OSがブートするためのコードしか含まれていないのだ。 まあこれはその通りで、これは良いと思う。
ページのクリアの部分にブレークポイントを貼って、ISSの動作を観察
ISSにブレークポイントを貼って、プログラムがどのようにロードされるのかを見ていこう。 僕のISSはGDBからブレークポイントを貼って、任意の場所の変数を観察することができるようになった。
|42 kmem.use_lock = 1; | |43 } | |44 | |45 void | |46 freerange(void *vstart, void *vend) | |47 { | |48 char *p; | |49 p = (char*)PGROUNDUP((uint)vstart); | |50 for(; p + PGSIZE <= (char*)vend; p += PGSIZE) | B+>|51 kfree(p); | |52 } | |53 | |54 //PAGEBREAK: 21 | |55 // Free the page of physical memory pointed at by v, | |56 // which normally should have been returned by a | |57 // call to kalloc(). (The exception is when | |58 // initializing the allocator; see kinit above.) | |59 void | |60 kfree(char *v) | |61 { | |62 struct run *r; | |63 |
これは、kfreeの入る直前の行に対してブレークポイントを貼っている。この状態でcontinueを入力すると、この部分まで進んで実行を停止する訳だ。ただし、ここではちょっとしたややこしい問題がある。
ブレークポイントに到達したプログラムは、該当行のプログラムを実行する前に停止しているのか、実行後に停止してるのか、どっちだ?
これについては明確な回答がある。
gcc+gdbによるプログラムのデバッグ 第2回 変数の監視、バックトレース、その他のコマンド
ここで注意するのは、ウォッチポイントは変更後にプログラムが停止します。 逆に、ブレークポイントはブレークポイントの行を実行前にプログラムが停止します。
言うのは簡単だが、これはプログラムとしてはどのように実現しているんだ?kfree()に到達したときに、PCは厳密にはどの位置にいるべきだろうか?そしてその命令は実行しているのか、実行された後なのか?
80104414: 00000000 nop 80104418: 8fc40010 lw a0,16(s8) <-- ここに貼られているが、この命令は実行すべきか、実行する前に停止するべきか? 8010441c: 0c041119 jal 80104464 <kfree> 80104420: 00000000 nop 80104424: 8fc20010 lw v0,16(s8) 80104428: 24421000 addiu v0,v0,4096 8010442c: afc20010 sw v0,16(s8)
この辺りはちゃんとした文献を見つけることができず、回答は得られなかった。GDBのスペシャリストを探して聞くのが明確かもしれないが。 だいたい実行前に停止するってどうするんだ?次に実行すべきプログラムカウンタの値を先読みするってことか?
まだ良くわからないが、とりあえずこの周辺でISSを停止させることはできる。このときのGDBのパケットのシーケンスは以下のようになっている。
<Return: initlock> <FunctionCall 293175: freerange(0x801043e4)> <Break PC:80104418> <---------- ここまではISSの関数トレース機能で動作を確認している Packet : p25 <---------- PCの確認。ここでいいのか?この命令は実行すべきか? PC = 80104418 Packet : g <---------- 全レジスタを検査 Packet : qL1200000000000000000 Packet : z0,80101420,4 <remove_pc_break : 80101420> Packet : z0,80104418,4 <---------- この場所のブレークポイントを解除 <remove_pc_break : 80104418> Packet : m8010d6cc,4 <---------- おそらくプロローグと、引数の状態を読み込んでいると思われる Packet : m8010d6cc,4 Packet : m8010d6c8,4 Packet : m8010d6e8,4 Packet : m8010d6d0,4 Packet : m8010d6ec,4 Packet : m8010d6d4,4 Packet : Z0,80101420,4 <set_pc_break : 80101420> Packet : Hc0 Packet : s <---------- ここで何故か1回ステップ実行をはさむ。ディレイスロット対策か? Current Step = 4794c Result Max_step= 1 <Proceed PC:80104418> Packet : p25 <---------- PCの取得 PC = 80104420 Packet : g Packet : m80104420,4 Packet : Z0,80104418,4 <set_pc_break : 80104418> Packet : Hc0 Packet : c <---------- プログラムの継続
一度入るステップ実行が謎すぎるのだが、これがもしディレイスロットを消費しているのだとすると、このときに正しくプログラムを動作させるためには、
- ブレークポイントに引掛った時点で、PCの値は+4(次の命令)になっている必要がある?
- 次のステップ実行により、スロット部分を実行している?
このあたりは自分でも明確な答えが得られておらず、調査が必要だ。