FPGA開発日記

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

Binary Translation型エミュレータを作る(レジスタアクセスを実現する)

同じ事を自作QEMUで実現しよう。自作QEMUが変換後のアセンブリコードを実行するとき、引数として仮想CPUのレジスタが格納されているアドレスの先頭を渡す。

    unsafe fn reflect(instructions: &[u8], gpr_base: *const [u64; 32]) {
        let map = match MemoryMap::new(
            instructions.len(),
            &[
...
        std::ptr::copy(instructions.as_ptr(), map.data(), instructions.len());

        let func: unsafe extern "C" fn(gpr_base: *const [u64; 32]) -> u32 =
            mem::transmute(map.data());

        let ans = func(gpr_base);        // 仮想CPUのGPRのアドレスを引数として渡してアセンブリ命令を実行。
        println!("ans = {:}", ans);
    }

プロローグとエピローグは本体の前に実行して、必要なスタック情報などを退避する。

        let host_prologue = [
            0x55, // pushq %rbp
            0x54, // pushq %rbx
            0x48, 0x8b, 0xef, // movq     %rdi, %rbp
            0x48, 0x81, 0xc4, 0x78, 0xfb, 0xff, 0xff, // addq     $-0x488, %rsp
        ];
        let host_epilogue = [
            0x48, 0x81, 0xc4, 0x88, 0x04, 0x00, 0x00, // addq     $0x488, %rsp
            0x5b, // popq     %rbx
            0x5d, // popq     %rbp
            0xc3, // retq
        ];

即値生成命令を変換する際、mov命令を生成する際にソースレジスタの位置をGPRの位置からオフセットで計算するようにした。

        if tcg.arg1.value == 0 {
            // if source register is x0, just generate immediate value.
            let revert_bytes = (tcg.arg2.value as u32).swap_bytes();
            // movl   imm,reg_addr(%rbp)
            let raw_mc: u64 = 0xc745_00_00000000 | (revert_bytes as u64) | (tcg.arg1.value << 32);
            return (raw_mc, 56 / 8);
        }

また、関数から戻るときはx10レジスタの値を%raxに渡すような命令を実行してからret命令を実行する。

    fn translate_ret(tcg: &TCGOp) -> (u64, usize) {
        assert_eq!(tcg.op, TCGOpcode::JMP);
        if tcg.arg0.t == TCGvType::Register
            && tcg.arg0.value == 0
            && tcg.arg1.t == TCGvType::Register
            && tcg.arg1.value == 1
        {
            // mov reg_off(%rbp), eax
            let raw_mc: u64 = 0x8b45_50;
            return (raw_mc, 3);
        }
        panic!("This function is not supported!")
    }

テストとして以下のRISC-V機械語を実行する。x10に100を代入して関数から戻ってくるだけの機械語だ。

        let riscv_guestcode: [u8; 8] = [
            0x13, 0x05, 0x40, 0x06, // addi a0,zero,100
            0x67, 0x80, 0x00, 0x00, // ret
        ];

実行結果は以下のようになった。

55 54 48 8b ef 48 81 c4 78 fb ff ff c7 45 50 64 00 00 00 8b 45 50 48 81 c4 88 04 00 00 5b 5d c3
ans = 100

結果が100となっており、機械語は正しく動作したことが分かる。変換されたコードを分解していくと、TCGが正しくx86のコードに変換されていることが確認できた。

55                       // pushq %rbp
54                      // pushq %rbx
48 8b ef                // movq     %rdi, %rbp
48 81 c4 78 fb ff ff    // addq     $-0x488, %rsp
c7 45 50 64 00 00 00    // movl     $100,0x50(%rbp)
8b 45 50                // movl     0x50(%rbp), %rax
48 81 c4 88 04 00 00    // addq     $0x488, %rsp
5b                      // popq     %rbx
5d                      // popq     %rbp
c3                      // retq
f:id:msyksphinz:20200819235741p:plain