Special instruction challenge チュートリアル

(エミュレータへの特殊命令の実装)

■ gdbのビルド方法

ここではgdbに付属する各種アーキテクチャのエミュレータを改造するため, 手始めにgdbのビルド方法を説明しておきます.

ここで用いるgdbのバージョンは7.12.1です.
まず,GDBのソースコードを入手して展開します.
ソースコードは以下からダウンロードできます.

https://othertools/cross-gcc494/gdb-7.12.1.tar.gz
MD5 (gdb-7.12.1.tar.gz) = 06c8f40521ed65fe36ebc2be29b56942
ダウンロードできたら展開します.
$ tar xvzf gdb-7.12.1.tar.gz
GDBのトップディレクトリに入ります.
$ cd gdb-7.12.1
以下で,Blackfin向けのgdbと,エミュレータをビルドします.
これでビルドされるのは,Blackfin向けプログラムをデバッグ/エミュレーション実行 するための,ビルドした環境上で動作するgdbです.
./configureのオプションは適宜調整してください.
以下は実際に競技サーバを構築した際のビルドオプションです.
$ ./configure --target=bfin-elf --disable-werror --disable-nls --with-python=no
$ make
同様に,ターゲットに以下を指定することで,他アーキテクチャ用の gdbがビルドできます.
bfin-elf msp430-elf mn10300-elf v850-elf mips64-elf
arm-elf rl78-elf microblaze-elf mips-elf aarch64-elf
ビルドが終わると,ディレクトリ gdb-7.12.1 以下に gdb/gdb というgdbの実行ファイルと, sim/bfin/run というエミュレータの実行ファイルが生成されています.

以下のようにすると,ビルドされたgdbやエミュレータを使って bfin-elf.xをデバッグしたり実行したりすることができます.

(gdbでのデバッグ)(チュートリアルの「静的解析してみる」を参照)

$ ./gdb/gdb ~/seccon/bfin-elf.x
(gdb) target sim --environment user
(gdb) load
(gdb) run
(エミュレータでの実行)
$ ./sim/bfin/run --environment user ~/seccon/bfin-elf.x

■ エミュレータへの特殊命令の実装

エミュレータへの実装については,サーバ構築用のファイルである サーバ構成ファイル一式(server.zip)中にあるパッチが実装例になります.

server.zip を展開すると,server/cross-gcc494/patch というフォルダに 以下のファイルがあります.

patch-gdb-7.12.1-include-opcode-msp430-decode.h
patch-gdb-7.12.1-include-opcode-msp430.h
patch-gdb-7.12.1-opcodes-microblaze-opc.h
patch-gdb-7.12.1-opcodes-microblaze-opcm.h
patch-gdb-7.12.1-opcodes-msp430-decode.c
patch-gdb-7.12.1-sim-aarch64-simulator.c
patch-gdb-7.12.1-sim-arm-armemu.c
patch-gdb-7.12.1-sim-bfin-bfin-sim.c
patch-gdb-7.12.1-sim-bfin-interp.c
patch-gdb-7.12.1-sim-common-nltvals.def
patch-gdb-7.12.1-sim-common-syscall.c
patch-gdb-7.12.1-sim-igen-gen-engine.c
patch-gdb-7.12.1-sim-microblaze-interp.c
patch-gdb-7.12.1-sim-microblaze-microblaze.isa
patch-gdb-7.12.1-sim-mips-interp.c
patch-gdb-7.12.1-sim-mips-mips.igen
patch-gdb-7.12.1-sim-mn10300-mn10300.igen
patch-gdb-7.12.1-sim-mn10300-op_utils.c
patch-gdb-7.12.1-sim-msp430-msp430-sim.c
patch-gdb-7.12.1-sim-rl78-mem.c
patch-gdb-7.12.1-sim-rl78-rl78.c
patch-gdb-7.12.1-sim-v850-simops.c
patch-gdb-7.12.1-sim-v850-v850.igen
競技サーバでは,runme.xを自動生成するために,10アーキテクチャぶんの特殊命令の 実装をしています.
それ以外にもGDBサーバで動作させるアーキテクチャに関しては, 安全のためにシステムコールを制限したり,命令の実行回数を制限したりする修正を 行っています.

このためパッチはたくさんあるのですが,ここではBlackfinについて, パッチを使って特殊命令の実装を体験してみましょう.

まずgdbを展開します.

$ tar xvzf gdb-7.12.1.tar.gz
GDBのトップディレクトリに入り,Blackfin用パッチを当てます.
Blackfin用には他にも patch-gdb-7.12.1-sim-bfin-interp.c というパッチがあるの ですが,こちらは内容を見るとシステムコール制限などのためのパッチのようなので, 実行するためだけなら不要です.
$ cd gdb-7.12.1
$ patch -p0 < ~/seccon/server/cross-gcc494/patch/patch-gdb-7.12.1-sim-bfin-bfin-sim.c
ビルドしてみます.
$ ./configure --target=bfin-elf --disable-werror --disable-nls --with-python=no
$ make
ビルドされたエミュレータを使って,bfin-elf.xを実行してみましょう.
$ ./sim/bfin/run --environment user ~/seccon/bfin-elf.x
SECCON{YouUseManyDSPWhileNotKnowing}
キーワードが出力されました.
特殊命令が正常に実行できているようです.

同様に,runme.xも実行してみてください.
(file runme.x を確認して,Blackfin向けに生成されているrunme.xを使ってください)
パスワード文字列が出力されるはずです.

他のアーキテクチャにもパッチ当てして,試してみてください.

■ エミュレータへの特殊命令の実装の内容

パッチの内容を確認してみましょう.
どのようなことをすれば,エミュレータに特殊命令を実装できるのでしょうか.

今回のアーキテクチャの中では,おそらくARM向けエミュレータが最もソースコードが 読みやすいので,ARMでの例を見てみましょう.

以下は,ARM向けの patch-gdb-7.12.1-sim-arm-armemu.c というパッチの内容です.

+#ifdef RANDOM_INSTRUCTION
+           case 0xf8:
+             rhs = DPRegRHS;
+             dest = linear32value(rhs);
+             WRITEDEST (dest);
+             break;
+
+           case 0xf9:
+             rhs = DPRegRHS;
+             dest = linear32seed(rhs);
+             WRITEDEST (dest);
+             break;
+
+           case 0xfa:
+             rhs = DPRegRHS;
+             dest = linear32param0(rhs);
+             WRITEDEST (dest);
+             break;
+
+           case 0xfb:
+             rhs = DPRegRHS;
+             dest = linear32param1(rhs);
+             WRITEDEST (dest);
+             break;
+#endif
前後を見るとわかるのですが,これは機械語コードの命令部分(オペコード)で switch〜caseにより分岐して,当該の命令を実行するという部分です.

最も簡単なエミュレータの実装は,このようなオペコードでのswitchと, 当該の命令を実行するためのcase,さらにswitchの前には命令となる機械語コードの 読み込み(フェッチ),そしてそれらを繰り返し実行するための無限ループから できています.

オペコードが f8〜fb のときを特殊命令として扱い, linear32value()などの関数を呼び出しています.
linear32value()の引数はDPRegRHS,戻り値は WRITEDEST() というマクロで処理 されています.これらは周りの命令に合わせて書いてあります.

オペコードが0x08のadd命令あたりを参考にすれば,どのような機械語コードの フォーマットで引数を指定すればいいかが推測できます.
ARMのadd命令は,gdb-7.12.1/sim/arm/armemu.c で以下のように実装されています.

            case 0x08:          /* ADD reg */
...
              rhs = DPRegRHS;
              dest = LHS + rhs;
              WRITEDEST (dest);
              break;
DPRegRHS や WRITEDEST() といったマクロは,乱数命令と同様のものが 使われていますね.

たとえばcross-gcc494の環境を利用してARMの add r1, r2, r3 や add r7, r8, r9 という命令の機械語コードを出力させると,以下のようになります.

(sample.c)
void direct()
{
  asm volatile ("add r1, r2, r3");
  asm volatile ("add r7, r8, r9");
  return;
}

(arm-elf.d)
00fe1678 <direct>:
  fe1678:       e0821003        add     r1, r2, r3
  fe167c:       e0887009        add     r7, r8, r9
  fe1680:       e1a0f00e        mov     pc, lr
オペコードは2〜3桁目の08の部分,その直後の1桁で第2オペランド, さらに1桁で第1オペランド,末尾の1桁に第3オペランドという 機械語コードになっているようです.

つまり乱数命令では,オペコードをパッチのcaseにあるf8〜fbとして, 引数を渡すレジスタは4桁目か末尾桁,値の格納先のレジスタは5桁目で指定すれば いいことになります.

ARMは第1引数をレジスタR0,戻り値もレジスタR0で渡します.
そしてARMの機械語コードは最上位4ビットに条件コードというものがあり, 命令ごとに条件実行ができるのですが,必ず実行させる場合はここを「e」にします.
そうすると引数をレジスタR0から受け取り,結果をレジスタR0で返すような 乱数取得命令は,以下のように書けます.

ef800000
パッチ中での乱数取得命令は case 0xf8 にありますから, 2〜3桁目のオペコード部分をf8としています. また3つ指定できるレジスタには,すべてゼロを指定しています.

ARM向けに生成された runme.x を見ると,実際の乱数命令の機械語コードが 上のようになっていることがわかります.

するとARM向けの乱数取得の関数は,以下のように書けるわけです.
(server.zip の exec-sim/rnd-arm-elf.c が以下のように実装されています)

unsigned int random_value(unsigned int value)
{
  asm volatile (".long 0xef800000");
  return value;
}

■ 他アーキテクチャでの特殊命令の実装

ARMは今回扱っているアーキテクチャの中で,GDB付属のエミュレータが 最も簡単な構造になっています.
このため改造しやすく,動作の理解も容易です.

他のアーキテクチャでの特殊命令の実装例については,パッチを参照してみて ください.
半分くらいのアーキテクチャでは,igenという命令デコード処理の自動生成の フレームワークが使われています.
このため記述は少なくて済みますが,難易度は高めです.
gdbをビルドするとigenからデコード処理が生成されるので,生成後のソースコードを 見たほうがいいかもしれません.
(また特殊命令を追加する場合にも,igenの生成元に追加するのでなく,生成後の ソースコードに対して改造を行う,という方法が楽でしょう)

さらに,ARMでは命令デコードの switch〜case がひとつだけで単純なのですが, この switch〜case が多段になっていて,グループごとにデコードしていく, という構成のアーキテクチャもあります.

cross-gcc494のアセンブラ解析環境と,あと実際にgdbのエミュレータにログなど 仕込んでビルドして命令実行時のログを見てみるなど,様々な手段を組み合わせて 解析するといいでしょう.

■ おしまい