前回の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); }
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
上手く行ったようだ。ディスアセンブル結果が表示された。