FPGA開発日記

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

Binary Translation型エミュレータを作る(ステップ実行のサポート)

Binary Translation方式の命令セットエミュレータのRust実装、ある程度進んできたが、どんどん複雑なテストパタンを確認していかなければならない。その際に問題となるのはデバッグ機能だ。デバッグ機能については、いくつかやらなければならないことがある。

  • ホスト命令をBasicBlock単位に変換するのではなく、1命令単位で変換して実行する。これにより命令毎にレジスタの値をダンプすることができるようになり、デバッグの幅が広がる。
  • ホスト命令のディスアセンブル機能。どのようなホスト命令が変換ターゲットとなるのか確認できる。
  • ゲスト命令のディスアセンブル機能。ホスト命令がどのようなゲスト命令に変換されたのかを確認できる。

この3つについて実装を進めていこうと思う。まずは1命令単位でエミュレーションをする機能について。

f:id:msyksphinz:20200930225126p:plain

通常QEMUをはじめとするBinary Translation型のエミュレータは、BasicBlock単位で命令の変換を行う。つまり、分岐命令に到達するまですべての命令をホスト命令に変換し、分岐命令に到達した時点で変換処理をいったん中止する。

この場合、やっかうなのがデバッグだ。分岐命令に到達するまで一気に命令が進むので、レジスタの状態などを命令毎にダンプすることができず、途中の結果などの細かな情報がどうしても取得することができない。

そこで、BasicBlock単位で変換を行うのではなく、命令毎に変換して、命令毎にブロックを区切ることによりそのたびにレジスタダンプなどが行えるようにする。これによりデバッグ時に取得できる情報が増える。

実際のコードを見る。通常は、いかの処理をブロックが終了するまで連続で実行している。

            #[allow(while_true)]
            while true {
                let guest_inst = unsafe {
                    ((self
                        .m_guest_text_mem
                        .data()
...
                let mut tcg_inst = TranslateRiscv::translate(id, &inst_info);
                self.m_tcg_vec.append(&mut tcg_inst);

self.m_tcg_vecに対してTCGを次々と追加し、それをX86命令に変換するのだが、デバッグ時は1つ分TCGを追加するだけで処理を中断する。

                let mut tcg_inst = TranslateRiscv::translate(id, &inst_info);
                self.m_tcg_vec.append(&mut tcg_inst);
                // これを追加。デバッグモードの時はすぐにTBを中断しホストコードに戻るTCGを追加する。
                if debug {
                    let mut exit_tcg = vec![TCGOp::new_0op(TCGOpcode::EXIT_TB)];
                    self.m_tcg_vec.append(&mut exit_tcg);
                }
...
                if debug {
                    break;      // When Debug Mode, break for each instruction
                }
            }

これにより、1命令毎に変換して処理が完了する。こうすれば1命令毎に様々な情報を出力することができるようになる。やってみよう。

$ cargo run -- --debug --dump-gpr \
    --elf-file /home/msyksphinz/work/riscv/riscv-tools/riscv-tests/build/isa/rv64ui-p-simple
========= BLOCK START =========
Guest PC Address = 00000000
  04c0006f : j       pc + 0x4c
tb_address  = 0x7f3476e00000
x00 = 0000000000000000  x01 = 0000000000000000  x02 = 0000000000000000  x03 = 0000000000000000
x04 = 0000000000000000  x05 = 0000000000000000  x06 = 0000000000000000  x07 = 0000000000000000
x08 = 0000000000000000  x09 = 0000000000000000  x10 = 0000000000000000  x11 = 0000000000000000
x12 = 0000000000000000  x13 = 0000000000000000  x14 = 0000000000000000  x15 = 0000000000000000
x16 = 0000000000000000  x17 = 0000000000000000  x18 = 0000000000000000  x19 = 0000000000000000
x20 = 0000000000000000  x21 = 0000000000000000  x22 = 0000000000000000  x23 = 0000000000000000
x24 = 0000000000000000  x25 = 0000000000000000  x26 = 0000000000000000  x27 = 0000000000000000
x28 = 0000000000000000  x29 = 0000000000000000  x30 = 0000000000000000  x31 = 0000000000000000

========= BLOCK START =========
Guest PC Address = 0000004c
  f1402573 : csrr    a0, mhartid
tb_address  = 0x7f3476dc0000
helper_csrrs(emu, 10, 0, 0xf14) is called!
x00 = 0000000000000000  x01 = 0000000000000000  x02 = 0000000000000000  x03 = 0000000000000000
x04 = 0000000000000000  x05 = 0000000000000000  x06 = 0000000000000000  x07 = 0000000000000000
x08 = 0000000000000000  x09 = 0000000000000000  x10 = 0000000000000000  x11 = 0000000000000000
x12 = 0000000000000000  x13 = 0000000000000000  x14 = 0000000000000000  x15 = 0000000000000000
x16 = 0000000000000000  x17 = 0000000000000000  x18 = 0000000000000000  x19 = 0000000000000000
x20 = 0000000000000000  x21 = 0000000000000000  x22 = 0000000000000000  x23 = 0000000000000000
x24 = 0000000000000000  x25 = 0000000000000000  x26 = 0000000000000000  x27 = 0000000000000000
x28 = 0000000000000000  x29 = 0000000000000000  x30 = 0000000000000000  x31 = 0000000000000000

========= BLOCK START =========
Guest PC Address = 00000050
  00051063 : bnez    a0, pc + 0
tb_address  = 0x7f3476df0000
x00 = 0000000000000000  x01 = 0000000000000000  x02 = 0000000000000000  x03 = 0000000000000000
x04 = 0000000000000000  x05 = 0000000000000000  x06 = 0000000000000000  x07 = 0000000000000000
x08 = 0000000000000000  x09 = 0000000000000000  x10 = 0000000000000000  x11 = 0000000000000000
x12 = 0000000000000000  x13 = 0000000000000000  x14 = 0000000000000000  x15 = 0000000000000000
x16 = 0000000000000000  x17 = 0000000000000000  x18 = 0000000000000000  x19 = 0000000000000000
x20 = 0000000000000000  x21 = 0000000000000000  x22 = 0000000000000000  x23 = 0000000000000000
x24 = 0000000000000000  x25 = 0000000000000000  x26 = 0000000000000000  x27 = 0000000000000000
x28 = 0000000000000000  x29 = 0000000000000000  x30 = 0000000000000000  x31 = 0000000000000000

こんな感じで、1命令ずつレジスタをダンプできるようになる。上手く行ったようだ。