FPGA開発日記

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

QEMUに入門してみる(18. ディスアセンブル機能の実装)

前回のQEMUの実行ログを見ると、命令のニーモニックが出力されていなかった。どうしたら命令のディスアセンブルが表示されるんだろう?ということでいろいろ調査した。

IN:
Priv: 3; Virt: 0
0x0000000000001000:
OBJD-T: 9702000093850202732540f1

このOBJD-Tというのがキーワードな気がする。これをQEMUのリポジトリで検索してみると、

  • qemu/disas.c
static int print_insn_od_target(bfd_vma pc, disassemble_info *info)
{
    return print_insn_objdump(pc, info, "OBJD-T");
}

print_insn_od_target()が呼ばれていることが分かった。さらにトレースしていくと、やはり以下のコールバック関数が登録されていないことが分かった。

  • qemu/target/myriscvx/cpu.c
static void myriscvx_cpu_class_init(ObjectClass *c, void *data)
{
  MYRISCVXCPUClass *mcc = MYRISCVX_CPU_CLASS(c);
  CPUClass *cc = CPU_CLASS(c);
  DeviceClass *dc = DEVICE_CLASS(c);

  device_class_set_parent_realize(dc, myriscvx_cpu_realize,
                                  &mcc->parent_realize);
...
#if defined(TARGET_MYRISCVX32)
  cc->gdb_core_xml_file = "myriscvx-32bit-cpu.xml";
#elif defined(TARGET_MYRISCVX64)
  cc->gdb_core_xml_file = "myriscvx-64bit-cpu.xml";
#endif
  cc->gdb_stop_before_watchpoint = true;
  // cc->disas_set_info = myriscvx_cpu_disas_set_info; このコードをコメントアウトしていた。
#ifndef CONFIG_USER_ONLY

このmyriscvx_cpu_disas_set_info()は以下のように実装している。

static void myriscvx_cpu_disas_set_info(CPUState *s, disassemble_info *info)
{
  info->print_insn = print_insn_myriscvx64;
}

print_insn_myriscvx64()は、どうやら手動で実装するらしい。まじか!

  • qemu/disas/myriscvx.c
/* types */

typedef uint64_t rv_inst;
typedef uint16_t rv_opcode;

...
static int
print_insn_myriscvx(bfd_vma memaddr, struct disassemble_info *info, rv_isa isa)
{
  char buf[128] = { 0 };
  bfd_byte packet[2];
  rv_inst inst = 0;
  size_t len = 2;
...
    
  disasm_inst(buf, sizeof(buf), isa, memaddr, inst);
  (*info->fprintf_func)(info->stream, "%s", buf);

  return len;
}
/* disassemble instruction */

static void
disasm_inst(char *buf, size_t buflen, rv_isa isa, uint64_t pc, rv_inst inst)
{
  rv_decode dec = { 0 };
  dec.pc = pc;
  dec.inst = inst;
  decode_inst_opcode(&dec, isa);
  decode_inst_operands(&dec);
  decode_inst_decompress(&dec, isa);
  decode_inst_lift_pseudo(&dec);
  format_inst(buf, buflen, 16, &dec);
}
f:id:msyksphinz:20200723013338p:plain:w300
disasm_inst()の実行フロー

decode_inst_opcode()は機械語ビット列に応じてdec->opを更新する。フォーマットは以下のように設定している。

...
#define rv_fmt_none                   "O\t"
#define rv_fmt_rs1                    "O\t1"
#define rv_fmt_offset                 "O\to"
#define rv_fmt_pred_succ              "O\tp,s"
#define rv_fmt_rs1_rs2                "O\t1,2"
#define rv_fmt_rd_imm                 "O\t0,i"
...

最終的にbufに命令フォーマットが生成される。これを出力するわけだ。

そして、MYRISCVXのdisas.cに登録する。

  • qemu/include/disas/dis-asm.h
int print_insn_myriscvx32       (bfd_vma, disassemble_info*);
int print_insn_myriscvx64       (bfd_vma, disassemble_info*);
  • qemu/disas/Makefile.objs
...
common-obj-$(CONFIG_RISCV_DIS) += riscv.o
common-obj-$(CONFIG_MYRISCVX_DIS) += myriscvx.o
common-obj-$(CONFIG_S390_DIS) += s390.o
...

これでリコンパイルして再びテストを実行した。

$ qemu-system-myriscvx64 --machine virt --d in_asm \
    --nographic --trace enable=myriscvx_trap \
    --kernel rv64ui-p-simple 2>&1 | tee qemu.myriscvx64.log
(qemu) ----------------
IN:
Priv: 3; Virt: 0
0x0000000000001000:  00000297          auipc           t0,0            # 0x1000
0x0000000000001004:  02028593          addi            a1,t0,32
0x0000000000001008:  f1402573          csrrs           a0,mhartid,zero

----------------
IN:
Priv: 3; Virt: 0
0x000000000000100c:  0182b283          ld              t0,24(t0)
0x0000000000001010:  00028067          jr              t0

----------------
IN:
Priv: 3; Virt: 0
0x0000000080000000:  04c0006f          j               76              # 0x8000004c

上手く行ったようだ。ディスアセンブル結果が表示された。