FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ISSとGDBを接続してxv6をデバッグする

前回まででGDBの環境はほぼ構築したので、xv6のイメージをロードして、デバッグしてみる。

msyksphinz.hatenablog.com

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ブレークポイントを貼って、プログラムがどのようにロードされるのかを見ていこう。 僕のISSGDBからブレークポイントを貼って、任意の場所の変数を観察することができるようになった。

   |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                                   <---------- プログラムの継続

一度入るステップ実行が謎すぎるのだが、これがもしディレイスロットを消費しているのだとすると、このときに正しくプログラムを動作させるためには、

  1. ブレークポイントに引掛った時点で、PCの値は+4(次の命令)になっている必要がある?
  2. 次のステップ実行により、スロット部分を実行している?

このあたりは自分でも明確な答えが得られておらず、調査が必要だ。

過去の記事

msyksphinz.hatenablog.com