読者です 読者をやめる 読者になる 読者になる

Linux MIPSの TLBミスハンドラを見る

linux mips qemu

Linux MIPSの TLBミス時に実行されるハンドラは
'arch/mips/mm/tlbex.c'で動的に生成されています。
なんでバイナリを逆アセンブルしてもわかりません。それが理解出来れば
何やってるかわかるんだろうけど、実際どんな命令列なんだって
ことでそれを調べる方法について書きます。


まず QEMUをインストールします。

  % git clone git://git.qemu.org/qemu.git qemu_git
  % cd qemu_git
  % ./configure --target-list=i386-softmmu,mipsel-softmmu,arm-softmmu
  (ターゲット名は ./default-configs/以下の .makファイル名.)
  % make 2>&1 |tee qemu_build.log
  % sudo paco -D make install

ターゲットは必要そうなやつだけにしてください。指定しないと長いです。
今回は MIPS el(little endian)が必要です。


次に GDBをインストールします。バージョンは 7.2を使いました。
もしかすると Binutilsがいるかもしれません。同じ configure
オプションでビルドできますので、なにか言われたらそうしてみて
ください。

  % cd gdb-7.2
  % mkdir build_mips && cd build_mips
  % ../configure --target=mipsel-linux-elf \
    --prefix=/home/syohei/local/mipsel_tools --disable-nls
  % make 2>&1 |tee build_mips.log
  % make install


QEMUで動く Linuxここから
mipsel-test-0.2.tar.gzを取得し、展開します。そしてデバッグモードで
QEMUを動かします。(-Sオプションがそうです。)

  % qemu-system-mipsel -S -s -kernel vmlinux-2.6.18-3-qemu
  -initrd initrd.gz -append "root=/dev/ram console=ttyS0 init=/bin/sh" \
  -nographic -M 'mips' -m 128

そして GDBを起動して、QEMUに接続します。
カーネルイメージは同じものを指定してください。

  % mipsel-linux-elf-gdb vmlinux-2.6.18-3-qemu
  (起動メッセージ)
  (gdb) target remote :1234
   Remote debugging using :1234
   0x8028b000 in kernel_entry ()

こんなメッセージが出たら大丈夫です。


MIPS32だと TLBミスが発生すると 0x80000000番地に飛ぶのでそこに
breakpointを仕掛けてプログラムを起動します。

  (gdb) break *0x80000000
  (gdb) c
   Continuing.

   Breakpoint 1, 0x80000000 in ?? ()

あとは PCから何命令か表示すればどんな命令列かわかります。

(gdb) x/20i $pc
=> 0x80000000:	lui	k1,0x802d
   0x80000004:	mfc0	k0,c0_badvaddr
   0x80000008:	lw	k1,8216(k1)
   0x8000000c:	srl	k0,k0,0x16
   0x80000010:	sll	k0,k0,0x2
   0x80000014:	addu	k1,k1,k0
   0x80000018:	mfc0	k0,c0_context
   0x8000001c:	lw	k1,0(k1)
   0x80000020:	srl	k0,k0,0x1
   0x80000024:	andi	k0,k0,0xff8
   0x80000028:	addu	k1,k1,k0
   0x8000002c:	lw	k0,0(k1)
   0x80000030:	lw	k1,4(k1)
   0x80000034:	srl	k0,k0,0x6
   0x80000038:	mtc0	k0,c0_entrylo0
   0x8000003c:	srl	k1,k1,0x6
   0x80000040:	mtc0	k1,c0_entrylo1
   0x80000044:	ehb
   0x80000048:	tlbwr
   0x8000004c:	eret

EntryLo0, EntryLo1に値を設定していたり、tlbwrをしていることから
間違いなさそうです。eretがあるんで、たぶんここで終わりでしょう。
その後は nopばっかりでした。


てかなんで Linuxはこの部分を動的生成しているんでしょうね。
アセンブリ言語で書いて、memcpyでぺろっとコピーすりゃいいじゃん
って思うんだけど、なにか理由があるんでしょう。

なおこれは古いカーネル(2.6.18)のものなんで、今は違う命令列かも
しれません。でも動的生成していることは変わりません。

NetBSDでは

一方 BSDではアセンブリ言語で書かれています。
以下は NetBSD 5.1の usr/src/sys/arch/mips/mips/mipsX_subr.Sの
コードです。該当アドレスへのコピーは同ディレクトリの mips_machdep.cの
mips3_vector_init関数で行っています。

VECTOR(MIPSX(TLBMiss), unknown)
        .set    noat
        mfc0    k0, MIPS_COP_0_BAD_VADDR        #00: k0=bad address
        lui     k1, %hi(segbase)                #01: k1=hi of segbase
        bltz    k0, 4f                          #02: k0<0 -> 4f (kernel fault)
        srl     k0, 20                          #03: k0=seg offset (almost)
        lw      k1, %lo(segbase)(k1)            #04: k1=segment tab base
        andi    k0, k0, 0xffc                   #05: k0=seg offset (mask 0x3)
        addu    k1, k0, k1                      #06: k1=seg entry address
        lw      k1, 0(k1)                       #07: k1=seg entry
        mfc0    k0, MIPS_COP_0_BAD_VADDR        #08: k0=bad address (again)
        beq     k1, zero, 5f                    #09: ==0 -- no page table
        srl     k0, 10                          #0a: k0=VPN (aka va>>10)
        andi    k0, k0, 0xff8                   #0b: k0=page tab offset
        addu    k1, k1, k0                      #0c: k1=pte address
        lw      k0, 0(k1)                       #0d: k0=lo0 pte
        lw      k1, 4(k1)                       #0e: k1=lo1 pte
        sll     k0, 2                           #0f: chop top 2 bits (part 1a)
        srl     k0, 2                           #10: chop top 2 bits (part 1b)
#ifdef MIPS3_5900
        mtc0    k0, MIPS_COP_0_TLB_LO0          #11: lo0 is loaded
        sync.p                                  #12: R5900 cop0 hazard
        sll     k1, 2                           #13: chop top 2 bits (part 2a)
        srl     k1, 2                           #14: chop top 2 bits (part 2b)
        mtc0    k1, MIPS_COP_0_TLB_LO1          #15: lo1 is loaded
        sync.p                                  #16: R5900 cop0 hazard
#else /* MIPS3_5900 */
        mtc0    k0, MIPS_COP_0_TLB_LO0          #11: lo0 is loaded
        sll     k1, 2                           #12: chop top 2 bits (part 2a)
        srl     k1, 2                           #13: chop top 2 bits (part 2b)
        mtc0    k1, MIPS_COP_0_TLB_LO1          #14: lo1 is loaded
        nop                                     #15: standard nop
        nop                                     #16: extra nop for QED5230
#endif /* MIPS3_5900 */
        tlbwr                                   #17: write to tlb
        nop                                     #18: standard nop
        nop                                     #19: needed by R4000/4400
        nop                                     #1a: needed by R4000/4400
        eret                                    #1b: return from exception
4:      j _C_LABEL(MIPSX(TLBMissException))     #1c: kernel exception
        nop                                     #1d: branch delay slot
5:      j       slowfault                       #1e: no page table present
        nop                                     #1f: branch delay slot
        .set    at
_VECTOR_END(MIPSX(TLBMiss))

ページテーブルの構成も違うんだろうけど、NetBSDのコードは TLBミスハンドラで
ページが見つからなかったら、フォールトハンドラのコードへ飛ぶみたい。
Linuxだとページがあろうがなかろうが、ページを突っ込んで、本当になければ
TLB invalid例外を発生させてそこでページテーブルの更新しているのかなと
思われます。


間違えていたらごめんなさい。