MIPSでリバースエンジニアリング

[Hello World](https://qiita.com/yamori813/items/130a2478766c75295348)するためにブートローダーのprintルーチンを探してみます。

プログラムが入っているSPI Flashを基板から剥がしてオープンソースのflashromで中身を読み出しファイルにしておきます。

このターゲットはブートするとSPI Flashの先頭64KByteがそのままメモリの0x80000000に貼り付けられ、実行が始まります。

読み出したバイナリファイルで、起動時に表示される文字列をhexdump -Cで探します。

```
0000b550  00 00 00 00 0a 2d 2d 2d  2d 2d 2d 2d 2d 2d 2d 2d  |.....-----------|
0000b560  2d 20 5b 53 50 4d 46 32  38 58 58 20 42 6f 6f 74  |- [SPMF28XX Boot|
0000b570  20 4c 6f 61 64 65 72 5d  20 2d 2d 2d 2d 2d 2d 2d  | Loader] -------|
0000b580  2d 2d 2d 2d 2d 00 00 00  31 20 6d 65 6d 50 72 6f  |-----...1 memPro|
```

先頭が0xb554の0x0a(改行)です。

strings -t xでも文字列のオフセットが確認できますが、この場合0x0aが省かれ、0x2dのオフセットになるのでhexdumpで探したほうがよかったです。

0xb554は実際は0x8000b554で、mipsでは16ビットずつ処理するので

0x8000(0000) + 0xb554

ですが、上位ビットが立っているのでマイナスになり

0x10000 - 0xb554 = 0x4aac

0x8001(0000) - 0x4aac

で10進では-19116です。

mips-objdump -D -b binary -m mips --adjust-vma=0x80000000で逆アセンブルしたデータでこれを探します。

```
80001c28:       0080023c        lui     v0,0x8000
80001c2c:       0180043c        lui     a0,0x8001
80001c30:       cd255024        addiu   s0,v0,9677
80001c34:       09f80002        jalr    s0
80001c38:       54b58424        addiu   a0,a0,-19116
```

この処理ではmipsの遅延スロットで80001c38が実行されてから80001c34のjalrが実行されます

a0が引数ポインターでs0が飛び先なんですが、s0の飛び先が0x800025cdと32bit境界からずれてるので、どこに飛んでいくのでしょうか？？？

回答：0x800025ccにある、mips16なコードに飛んでいくようです。下位1ビットはmips16をしめしているようです。

objdumpを-m mips16にするとmips16で逆アセンブルできます。

```
    800025cc:   fd63            addiu   sp,-24
    800025ce:   04d0            sw      s0,16(sp)
    800025d0:   0562            sw      ra,20(sp)
    800025d2:   0467            move    s0,a0
    800025d4:   80a0            lbu     a0,0(s0)
    800025d6:   ff6b            li      v1,255
    800025d8:   4467            move    v0,a0
    800025da:   6cea            and     v0,v1
    800025dc:   0522            beqz    v0,0x800025e9
    800025de:   07b2            lw      v0,0x800025f8
    800025e0:   6cec            and     a0,v1
    800025e2:   40ea            jalr    v0
```

ブートローダーはSOC内のプリブートローダーのコードでSPI Flashの先頭64Kbyteがメモリに貼り付けられて0x200から実行されるようです。

```
80000200:       00d01d3c        lui     sp,0xd000
80000204:       fc1fbd27        addiu   sp,sp,8188
80000208:       01801c3c        lui     gp,0x8001
8000020c:       90409c27        addiu   gp,gp,16528
80000210:       04010008        j       0x80000410
80000214:       00000000        nop
```

spとgpを設定して本体に飛んでるように見えるのですが、spの0xd0001ffcってメモリあるのかな？メモリは0x80000000-0x80800000だと思うんだけど。MIPSはKSEG0がこれで、キャッシュが効かないKSEG1が0xa0000000-0xa0800000と理解しているのだが。

例外処理は

```
80000280:       00801a3c        lui     k0,0x8000
80000284:       84075a27        addiu   k0,k0,1924
80000288:       08004003        jr      k0
8000028c:       00000000        nop
```

で実体は0x80000784にあります。

Cの関数はアセンブラでは以下のように始まります。

```
80000560:       e8ffbd27        addiu   sp,sp,-24
80000564:       00b0023c        lui     v0,0xb000
80000568:       1000b0af        sw      s0,16(sp)
8000056c:       1400bfaf        sw      ra,20(sp)
```

関数の終わりにspを戻します。

```
80000658:       1400bf8f        lw      ra,20(sp)
8000065c:       1000b08f        lw      s0,16(sp)
80000660:       0800e003        jr      ra
80000664:       1800bd27        addiu   sp,sp,24
```

jalrはraに戻りアドレスが入っているのでjr raで元に戻れます。関数内でjrで別の場所に飛んで最後にjr raすることもあります。

プログラムを書いて、mips-gccでコンパイルしたものをmips-objdumpで逆アセンブラしてみるのも参考になります。mipsはC言語とアセンブラがかなり違っているので、根気よく確認するしかありません。

IOの設定などはsb命令で実行されるのでアドレスの一覧を拾い出すスクリプトをawkで書いてみました。

```awk
{
if ($3 == "lui") {
split($4, arg, ",")
reg[arg[1]] = arg[2]
}
if ($3 == "sb") {
u = substr( $4, index( $4, "(" ) + 1, 2)
o = index( $4, "," )
l = substr( $4, o + 1, index( $4, "(" ) - o - 1)
if (l < 0) {
l  = 65536 + l
}
if (reg[u] != "") {
printf("%s%04x\n",  reg[u], l)
}
}
}
```

objdump -Dしたものを食わせます。

RT3050のu-bootのコードでuartの出力は

```c
/*
 * Output a single byte to the serial port.
 */
void serial_putc (const char c)
{
        /* wait for room in the tx FIFO on UART */
        while ((LSR(CFG_RT2880_CONSOLE) & LSR_TEMT) == 0);

        TBR(CFG_RT2880_CONSOLE) = c;

        /* If \n, also do \r */
        if (c == '\n')
                serial_putc ('\r');
}
```

アセンブラでは

```
000000e8 <serial_putc>:
  e8:   3c1c0000        lui     gp,0x0
  ec:   279c0000        addiu   gp,gp,0
  f0:   0399e021        addu    gp,gp,t9
  f4:   3c05b000        lui     a1,0xb000
  f8:   00042600        sll     a0,a0,0x18
  fc:   00042603        sra     a0,a0,0x18
 100:   34a30c1c        ori     v1,a1,0xc1c
 104:   8c620000        lw      v0,0(v1)
 108:   30420040        andi    v0,v0,0x40
 10c:   1040fffd        beqz    v0,104 <serial_putc+0x1c>
 110:   34a20c04        ori     v0,a1,0xc04
 114:   ac440000        sw      a0,0(v0)
 118:   2402000a        li      v0,10
 11c:   14820003        bne     a0,v0,12c <serial_putc+0x44>
 120:   8f990000        lw      t9,0(gp)
 124:   03200008        jr      t9
 128:   2404000d        li      a0,13
 12c:   03e00008        jr      ra
 130:   00000000        nop
```
MIPSでIOをアクセスする時の方法はluiしてoriして直接アドレスを作る方法と、luiしてsb,sh,swなどでオフセットを指定する方法があります。