Binary Translation方式の命令セットエミュレータのRust実装をしている。前回まででデバッグ機能は作り切ったので、いよいよより複雑な機能の実装に着手する。まず手を付けなければならないのは仮想アドレスのサポート。前回までにとりあえずRustによる仮想アドレス変換の実装を行った。仮想アドレス変換はロードストア命令に対しても実装しなければならない。しかし現在のTCGの構成のままロードストア命令にアドレス変換を実装するといきなり実装が複雑になりデバッグが大変になるので、一度ロードストア命令をすべてヘルパー関数による実装に変更し、メモリアクセスをすべてヘルパー関数に置き換えることにした。これによりメモリアクセスのエミュレーション速度は著しく低下するが、確実にデバッグできる環境になるのでこれでまずはテストパタンを通していきたい。
これまで、メモリアクセス命令は以下のようなTCGを生成していた。
pub fn translate_ld(inst: &InstrInfo) -> Vec<TCGOp> { Self::translate_rri(TCGOpcode::LOAD_64BIT, inst) } pub fn translate_lw(inst: &InstrInfo) -> Vec<TCGOp> { Self::translate_rri(TCGOpcode::LOAD_32BIT, inst) }
単純なTCGであるが、TCGOpcode::LOAD_64BIT
などのオペコードはx86への変換部分でネイティブなコードに落とし込んでいた。
fn tcg_gen_load( emu: &EmuEnv, pc_address: u64, tcg: &TCGOp, mc: &mut Vec<u8>, mem_size: MemOpType, target_reg: RegisterType, ) -> usize { let mut gen_size: usize = pc_address as usize; let arg0 = tcg.arg0.unwrap(); ... // Load Guest Memory Head into EAX let guestcode_addr = emu.calc_guest_data_mem_address(); gen_size += Self::tcg_gen_imm_u64(X86TargetRM::RAX, guestcode_addr as u64, mc); // Move Guest Memory from EAX to ECX gen_size += Self::tcg_modrm_64bit_out( X86Opcode::MOV_GV_EV, X86ModRM::MOD_11_DISP_RAX, X86TargetRM::RCX, mc, ); // Load value from rs1 gen_size += Self::tcg_modrm_64bit_out( X86Opcode::MOV_GV_EV, X86ModRM::MOD_10_DISP_RBP, X86TargetRM::RAX, mc, ); gen_size += Self::tcg_out(emu.calc_gpr_relat_address(arg1.value) as u64, 4, mc); ...
これを、以下のようにTCGへの変換部分をヘルパー関数呼び出しに置き換えてしまった。
pub fn translate_ld(inst: &InstrInfo) -> Vec<TCGOp> { let rs1_addr: usize = get_rs1_addr!(inst.inst) as usize; let imm_const: u64 = ((inst.inst as i32) >> 20) as u64; let rd_addr: usize = get_rd_addr!(inst.inst) as usize; let rs1 = Box::new(TCGv::new_reg(rs1_addr as u64)); let imm = Box::new(TCGv::new_imm(imm_const)); let rd = Box::new(TCGv::new_reg(rd_addr as u64)); let op = TCGOp::new_helper_call_arg3(CALL_HELPER_IDX::CALL_LOAD64_IDX as usize, *rd, *rs1, *imm); vec![op] }
ヘルパー関数は、この例ではCALL_LOAD64_IDX
を呼び出している。これは以下のように実装している。
pub fn helper_func_load16( emu: &mut EmuEnv, rd: u32, rs1: u32, imm: u32, _dummy: u32, ) -> usize { let rs1_data = emu.m_regs[rs1 as usize]; let addr = rs1_data.wrapping_add(imm as i32 as u64); #[allow(unused_assignments)] let mut guest_phy_addr:u64 = 0; match emu.convert_physical_address(addr, MemAccType::Read) { Ok(addr) => guest_phy_addr = addr, Err(error) => { panic!("Read Error: {:?}\n", error); } }; emu.m_regs[rd as usize] = emu.read_mem_4byte(guest_phy_addr) as u64; return 0; }
もろにRust実装を呼び出している。convert_physical_address()
は前回実装したものだ。ネイティブコードを使わずRustの実装により物理アドレスを求め、メモリアクセスを行ってその結果をレジスタに代入する。
まずはこの実装でデグレードが無いことを確認する。
$ cargo run -- --debug --dump-gpr --elf-file /home/msyksphinz/work/riscv/riscv-tools/riscv-tests/build/isa/rv64ui-p-add
Guest PC Address = 80000040 <Convert_Virtual_Address. virtual_addr=0000000080000040 : vm_mode = 0, priv_mode = 3> converted physical address = 80000040 00001f17 : auipc t5, 0x1 <Convert_Virtual_Address. virtual_addr=0000000080000044 : vm_mode = 0, priv_mode = 3> converted physical address = 80000044 fc3f2023 : sw gp, -64(t5) <Convert_Virtual_Address. virtual_addr=0000000080000048 : vm_mode = 0, priv_mode = 3> converted physical address = 80000048 ff9ff06f : j pc + 0xffff8 tb_address = 0x7ffa59cb0000
上手く行っているようだ。次は仮想アドレスの確認。
$ cargo run -- --debug --dump-gpr --elf-file /home/msyksphinz/work/riscv/riscv-tools/riscv-tests/build/isa/rv64ui-v-add
x20 = 0000000000000000 x21 = 0000000000000000 x22 = 0000000000000000 x23 = 0000000000000000 x24 = 0000000000000000 x25 = 0000000000000000 x26 = 0000000000000000 x27 = 0000000000000000 x28 = 0000000000000000 x29 = 0000000000000000 x30 = 0000000000000000 x31 = 0000000000000000 Result: MEM[0x1000] = 00000000 ...
あれ、テストが成功しなかった。問題を確認していく。