FPGA開発日記

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

Binary Translation型エミュレータを作る(RISC-V Disassemblerクレートの作成)

Binary Translation方式の命令セットエミュレータのRust実装、ある程度進んできたが、どんどん複雑なテストパタンを確認していかなければならない。デバッグ機能についてだが、ホスト命令のディスアセンブル機能ができたので、次はゲスト命令をディスアセンブル表示できるようにしたい。ゲスト命令については今回はRISC-Vに絞っているため、RISC-Vのディスアセンブラで既に使用できるRust実装が無いか探したが、うまそうなものが無いため自作することにした。

と言ってもデコーダを1から作ってしまうのは非常に面倒なので、spike-dasmというC++で記述されたRISC-VデコーダをRustのWrapperで囲んでRustのインタフェースを作ることにした。

まずはspike-dasmについて調査する。riscv-isa-simリポジトリに実体がある。

github.com

この中でディスアセンブル機能の実体を探す。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_disassemble()から文字列を返すのがどうも上手く行かず、必ず文字が崩れてしまうのでもしやと思って明示的にメモリ中に領域を確保してそこに文字列をコピーするようにしたらうまく行った。これはもしかしてspike-dasm自体のメモリリークのバグかもしれない。。。

という訳でこのラッパーをクレートして用意することにした。

https://crates.io/crates/spike-dasm-wrapper

github.com

活用方法は以下だ。単純に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

いいね、ディスアセンブル結果がしっかりと表示された。これでデバッグはかなりやりやすくなる。

f:id:msyksphinz:20200930225648p:plain