(エミュレータへの特殊命令の実装)
ここでは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.gzGDBのトップディレクトリに入ります.
$ cd gdb-7.12.1以下で,Blackfin向けのgdbと,エミュレータをビルドします.
$ ./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アーキテクチャぶんの特殊命令の 実装をしています.
このためパッチはたくさんあるのですが,ここではBlackfinについて, パッチを使って特殊命令の実装を体験してみましょう.
まずgdbを展開します.
$ tar xvzf gdb-7.12.1.tar.gzGDBのトップディレクトリに入り,Blackfin用パッチを当てます.
$ 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のエミュレータにログなど 仕込んでビルドして命令実行時のログを見てみるなど,様々な手段を組み合わせて 解析するといいでしょう.