TCGは分岐命令に到達するまで一連のブロックとして変換されるため、1ブロックで複数の命令が実行されます。もしこのブロック実行中に例外が発生した場合どのようにすればよいでしょうか?例えば以下のような命令列をTCGに変換することを考えます。
add x10, x10, 1 ld x10, 0(x10) sret
LD命令で例外が発生すると、MEPCに現在のプログラムカウンタを保存して例外ルーチンにジャンプする必要があります。しかしこのブロックを変換した時点でTCGにプログラムカウンタの情報は保存されていません。例外が発生した場合に、MEPCにどのようにプログラムカウンタを通知すればよいのか問題になります。
これを解決するために、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 tcg_inst_addr = Box::new(TCGv::new_imm(inst.addr)); let op = TCGOp::new_helper_call_arg4(CALL_HELPER_IDX::CALL_LOAD64_IDX as usize, *rd, *rs1, *imm, *tcg_inst_addr); vec![op] // Self::translate_rri(TCGOpcode::LOAD_64BIT, inst) }
ヘルパー関数の呼び出しですが、引数を増やして*tcg_inst_addr
を第4引数に追加しました。tcg_inst_addr
は現在のプログラムカウンタinst.addr
を保存しており、例外発生時にこの値(TCG変換時に明らかになっているゲストマシンのプログラムカウンタ)が渡されます。
これによりロード命令であれば、ページテーブルウォーク時にページテーブルエラーが発生した際、エラーの発生したアドレスを引数としてもらっているためMEPCに正しい値を設定可能になります。
pub fn helper_func_load64( emu: &mut EmuEnv, rd: u64, rs1: u64, imm: u64, guest_pc: u64, ) -> usize { let rs1_data = emu.m_regs[rs1 as usize]; let addr = rs1_data.wrapping_add(imm as i32 as u64); println!("load64 : converted address: {:016x}", addr); #[allow(unused_assignments)] let mut guest_phy_addr :u64 = 0; match emu.convert_physical_address(guest_pc, addr, MemAccType::Read) { Ok(addr) => { guest_phy_addr = addr; println!("load64 : converted address: {:016x} --> {:016x}", addr, guest_phy_addr); emu.m_regs[rd as usize] = emu.read_mem_8byte(guest_phy_addr) as u64; } Err(error) => { print!("Read Error: {:?}\n", error); emu.generate_exception(guest_pc, ExceptCode::LoadPageFault, addr as i64); } }; return 0; }
pub fn generate_exception(&mut self, guest_pc: u64, code: ExceptCode, tval: i64) { println!( "<Info: Generate Exception Code={}, TVAL={:016x} PC={:016x}>", code as u32, tval, guest_pc ); let epc = guest_pc; ... if (medeleg & (1 << (code as u32))) != 0 { // Delegation self.m_csr.csrrw(CsrAddr::Sepc, epc as i64); self.m_csr.csrrw(CsrAddr::Scause, code as i64); self.m_csr.csrrw(CsrAddr::Stval, tval as i64); tvec = self.m_csr.csrrs(CsrAddr::Stvec, 0 as i64); next_priv = PrivMode::Supervisor; } else { self.m_csr.csrrw(CsrAddr::Mepc, epc as i64); self.m_csr.csrrw(CsrAddr::Mcause, code as i64); self.m_csr.csrrw(CsrAddr::Mtval, tval as i64); tvec = self.m_csr.csrrs(CsrAddr::Mtvec, 0 as i64); print!("tvec = {:016x}\n", tvec); }
この実装でテストプログラムを走らせてみた。
$ cargo run -- --step --dump-gpr --elf-file /home/msyksphinz/work/riscv/riscv-tools/riscv-tests/build/isa/rv64ui-v-lw
========= BLOCK START ========= 11999: Guest PC Address = ffffffffffe02258 <Convert_Virtual_Address. virtual_addr=ffffffffffe02258 : vm_mode = 8, priv_mode = 1> <Info: VAddr = 0xffffffffffe02258 PTEAddr = 0x0000000080004ff8 : PPTE = 0x20001801> <Info: VAddr = 0xffffffffffe02258 PTEAddr = 0x0000000080006ff8 : PPTE = 0x200000cf> converted physical address = 80002258 0000006f : j pc + 0x0 tb_address = 0x7fe8ba390000 00007FE8BA390000 48B85822E0FFFFFFFFFF movabs $0xFFFF_FFFF_FFE0_2258,%rax 00007FE8BA39000A 48898508020000 mov %rax,0x208(%rbp) 00007FE8BA390000 E9F9FF0800 jmp 0x0000_7FE8_BA41_FFFE 00007FE8BA390000 E9F4FF0800 jmp 0x0000_7FE8_BA41_FFF9 x00 = 0000000000000000 x01 = ffffffffffe028ac x02 = ffffffffffe09660 x03 = 0000000000000003 x04 = 0000000000000000 x05 = 0000000000000008 x06 = 0000000000000000 x07 = ffffffffff00ff00 x08 = 000000000003f000 x09 = 00000000000003e0 x10 = 0000000000000007 x11 = ffffffffffe04000 x12 = ffffffffffe04000 x13 = 0000000000004000 x14 = 0000000000000000 x15 = ffffffffffe01250 x16 = 0000000000001000 x17 = 0000000000000000 x18 = 000000000003f000 x19 = 0000000000000007 x20 = ffffffffffe087e0 x21 = ffffffffffe00000 x22 = 0000000000040000 x23 = ffffffffffe04000 x24 = 0000000020010c5f x25 = 0000000000000000 x26 = ffffffffffe083f0 x27 = ffffffffffe03000 x28 = 0000000000000000 x29 = 0000000000000000 x30 = 0000000000000000 x31 = 0000000000000000 Result: MEM[0x1000] = 00000007
何とか完走するようになったが、まだテストパタンがFailする。例外の処理を正しく実装しないとまだ難しいなあ。