Binary Translation方式の命令セットエミュレータのRust実装、ある程度進んできたが、どんどん複雑なテストパタンを確認していかなければならない。デバッグ機能についてだが、ホスト命令のディスアセンブル機能ができたので、次はゲスト命令をディスアセンブル表示できるようにしたい。ゲスト命令については今回はRISC-Vに絞っているため、RISC-Vのディスアセンブラで既に使用できるRust実装が無いか探したが、うまそうなものが無いため自作することにした。
と言ってもデコーダを1から作ってしまうのは非常に面倒なので、spike-dasm
というC++で記述されたRISC-VデコーダをRustのWrapperで囲んでRustのインタフェースを作ることにした。
まずはspike-dasm
について調査する。riscv-isa-sim
リポジトリに実体がある。
この中でディスアセンブル機能の実体を探す。diasm.cc
とかdisam.h
とかが怪しい。
riscv-isa-sim/spike_main/spike-dasm.cc
disassembler_t* disassembler = new disassembler_t(xlen); ... string dis = disassembler->disassemble(bits); s = s.substr(0, start) + dis + s.substr(endp - &s[0] + 1); pos = start + dis.length(); }
このdisassembler_t
というものが本質らしい。このクラスをRustで呼び出せるように改造する。
spike-dasm-wrapper/src/lib.rs
#[link(name = "spike-dasm", kind="static")] use std::ffi::CStr; use std::os::raw::c_char; pub enum DisasmImpl {} extern { pub fn Disasm_Disasm() -> *mut DisasmImpl; pub fn Disasm_disassemble(diasm: *mut DisasmImpl, insn:u32) -> *const c_char; } pub struct Disasm { raw: *mut DisasmImpl } impl Disasm { #[inline] pub fn new() -> Self { unsafe { Disasm { raw: Disasm_Disasm()}} } #[inline] pub fn disassemble(&mut self, insn: u32) ->String { let st = unsafe { let st_raw = Disasm_disassemble(self.raw, insn); CStr::from_ptr(st_raw).to_string_lossy().into_owned() }; return st; } }
ここでは、C言語側とのインタフェースとしてDiasm_Diasm()
とDisasm_disasemble()
を定義している。この関数の実体はこちら。
spike-dasm-wrapper/src/helper.cpp
#include <iostream> #include "disasm.h" extern "C" { typedef struct { disassembler_t impl; } DisasmImpl; DisasmImpl *Disasm_Disasm() { disassembler_t *diasm = new disassembler_t(64); return (DisasmImpl *)diasm; } const char *Disasm_disassemble(DisasmImpl *di, uint32_t insn) { auto str = di->impl.disassemble(insn); char *str_ptr = new char[100]; memcpy(str_ptr, str.c_str(), str.length()); return str_ptr; } }
それぞれ、
Disasm_Disasm()
はdiassembler_t()
クラスをインスタンス化する。Diasm_disassemble()
はインスタンス化したクラスを使ってディスアセンブルを行い、その結果の文字列を返す。
このときに、Disasm_disassemble()
から文字列を返すのがどうも上手く行かず、必ず文字が崩れてしまうのでもしやと思って明示的にメモリ中に領域を確保してそこに文字列をコピーするようにしたらうまく行った。これはもしかしてspike-dasm
自体のメモリリークのバグかもしれない。。。
という訳でこのラッパーをクレートして用意することにした。
https://crates.io/crates/spike-dasm-wrapper
活用方法は以下だ。単純にDisasm
クラスをインスタンス化して呼び出しているだけだ。とりあえずこれでうまく行っているが、これだとディスアセンブルを実行する度に新規インスタンスを作っているので効率が悪い。そのうち何とかしたい。
pub fn disassemble_riscv(inst: u32) -> String { let mut disasm = Disasm::new(); disasm.disassemble(inst) }
以下のようにしてゲスト命令をフェッチしている最中に呼び出す。
self.m_tcg_vec.append(&mut tcg_inst); if step { let mut exit_tcg = vec![TCGOp::new_0op(TCGOpcode::EXIT_TB)]; self.m_tcg_vec.append(&mut exit_tcg); } if debug { print!(" {:08x} : {}\n", inst_info.inst, disassemble_riscv(guest_inst)); } if id == RiscvInstId::JALR || id == RiscvInstId::JAL ...
実行結果は以下のようになった。x86ホスト命令のディスアセンブルと同時に呼び出してみる。
cargo run -- --debug --elf-file \ /home/msyksphinz/work/riscv/riscv-tools/riscv-tests/build/isa/rv64ui-p-simple
========= BLOCK START ========= Guest PC Address = 00000054 00000297 : auipc t0, 0x0 01028293 : addi t0, t0, 16 30529073 : csrw mtvec, t0 18005073 : csrwi satp, 0 00000297 : auipc t0, 0x0 01c28293 : addi t0, t0, 28 30529073 : csrw mtvec, t0 fff00293 : li t0, 4095 3b029073 : csrw pmpaddr0, t0 01f00293 : li t0, 31 3a029073 : csrw pmpcfg0, t0 00000297 : auipc t0, 0x0 01828293 : addi t0, t0, 24 30529073 : csrw mtvec, t0 30205073 : csrwi medeleg, 0 30305073 : csrwi mideleg, 0 30405073 : csrwi mie, 0 00000193 : li gp, 0 00000297 : auipc t0, 0x0 f6828293 : addi t0, t0, 3944 30529073 : csrw mtvec, t0[f:id:msyksphinz:20200930225648p:plain] 00100513 : li a0, 1 01f51513 : slli a0, a0, 31 00055863 : bgez a0, pc + 16 tb_address = 0x7fadcfb40000 00007FADCFB40000 48C7853000000054000000 movq $0x54,0x30(%rbp) 00007FADCFB40000 488B8530000000 mov 0x30(%rbp),%rax 00007FADCFB40007 480510000000 add $0x10,%rax 00007FADCFB4000D 48898530000000 mov %rax,0x30(%rbp) 00007FADCFB40000 48BF5034A3F3FF7F0000 movabs $0x7FFF_F3A3_3450,%rdi 00007FADCFB4000A 48BE0000000000000000 movabs $0,%rsi 00007FADCFB40014 48BA0500000000000000 movabs $5,%rdx 00007FADCFB4001E 48B90503000000000000 movabs $0x305,%rcx
いいね、ディスアセンブル結果がしっかりと表示された。これでデバッグはかなりやりやすくなる。