同じ事を自作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