FPGA開発日記

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

QEMUに入門してみる(12. QEMUでのコールバック関数追加方法の調査)

QEMUの続き。QEMUのRISC-V対応においてバイナリのシミュレーション方法とログの出し方が分かったわけだが、これをMYRISCVXに移植したい。つまり同じコマンドをqemu-system-myriscvx64に適用することを考える。

まずは変換のための初期化関数から。

  • qemu/target/myriscvx/cpu.c
static void myriscvx_cpu_class_init(ObjectClass *c, void *data)
...
#ifdef CONFIG_TCG
  cc->tcg_initialize = myriscvx_translate_init;
  // cc->tlb_fill = myriscvx_cpu_tlb_fill;
#endif
  • qemu/target/myriscvx/translate.c
void myriscvx_translate_init(void)
{
  int i;

  /* cpu_gpr[0] is a placeholder for the zero register. Do not use it. */
  /* Use the gen_set_gpr and gen_get_gpr helper functions when accessing */
  /* registers, unless you specifically block reads/writes to reg 0 */
  cpu_gpr[0] = NULL;

  for (i = 1; i < 32; i++) {
    cpu_gpr[i] = tcg_global_mem_new(cpu_env,
                                    offsetof(CPUMYRISCVXState, gpr[i]), myriscvx_int_regnames[i]);
  }

  cpu_pc = tcg_global_mem_new(cpu_env, offsetof(CPUMYRISCVXState, pc), "pc");
  load_res = tcg_global_mem_new(cpu_env, offsetof(CPUMYRISCVXState, load_res),
                                "load_res");
  load_val = tcg_global_mem_new(cpu_env, offsetof(CPUMYRISCVXState, load_val),
                                "load_val");
}

cpu_gprcpu_pcなどの変数はグローバル変数として定義してある。これは何のために必要なんだろう?

  • qemu/target/myriscvx/translate.c
/* global register indices */
static TCGv cpu_gpr[32], cpu_pc;
// static TCGv_i64 cpu_fpr[32]; /* assume F and D extensions */
static TCGv load_res;
static TCGv load_val;

そもそもTCGvという型名の意味は、Tiny Code Generator Variableのことらしい。

  • qemu/include/tcg/tcg.h
typedef struct TCGv_i32_d *TCGv_i32;
typedef struct TCGv_i64_d *TCGv_i64;
typedef struct TCGv_ptr_d *TCGv_ptr;
typedef struct TCGv_vec_d *TCGv_vec;

とりあえず、これらの変数を確保するらしい。この状態でビルドして動作を確認する。

qemu-system-myriscvx64 --machine virt --d in_asm --nographic --kernel rv64ui-p-simple
QEMU 5.0.0 monitor - type 'help' for more information
(qemu) zsh: segmentation fault (core dumped)   --machine virt --d in_asm --nographic --kernel

うーん、落ちた。分からないのでGDBでチェックする。

#0  0x0000000000000000 in  ()
#1  0x000000000048b7a5 in tlb_fill (cpu=0xc78fc0, addr=0, size=0, access_type=MMU_INST_FETCH, mmu_idx=0, retaddr=0) at /home/msyksphinz/work/riscv/qemu/accel/tcg/cputlb.c:1017
#2  0x000000000048bdfb in get_page_addr_code_hostp (env=0xc819e0, addr=0, hostp=0x0) at /home/msyksphinz/work/riscv/qemu/accel/tcg/cputlb.c:1172
#3  0x000000000048bf22 in get_page_addr_code (env=0xc819e0, addr=0) at /home/msyksphinz/work/riscv/qemu/accel/tcg/cputlb.c:1204
#4  0x00000000004a4195 in tb_htable_lookup (cpu=0xc78fc0, pc=0, cs_base=0, flags=0, cf_mask=4278190080) at /home/msyksphinz/work/riscv/qemu/accel/tcg/cpu-exec.c:337
#5  0x00000000004a36da in tb_lookup__cpu_state (cpu=0xc78fc0, pc=0x7fffb3fef538, cs_base=0x7fffb3fef530, flags=0x7fffb3fef52c, cf_mask=4278190080)
    at /home/msyksphinz/work/riscv/qemu/include/exec/tb-lookup.h:43
#6  0x00000000004a444a in tb_find (cpu=0xc78fc0, last_tb=0x0, tb_exit=0, cf_mask=0) at /home/msyksphinz/work/riscv/qemu/accel/tcg/cpu-exec.c:404
#7  0x00000000004a4d1d in cpu_exec (cpu=0xc78fc0) at /home/msyksphinz/work/riscv/qemu/accel/tcg/cpu-exec.c:731
#8  0x0000000000468061 in tcg_cpu_exec (cpu=0xc78fc0) at /home/msyksphinz/work/riscv/qemu/cpus.c:1405
#9  0x000000000046835a in qemu_tcg_rr_cpu_thread_fn (arg=0xc78fc0) at /home/msyksphinz/work/riscv/qemu/cpus.c:1507
#10 0x0000000000707e5e in qemu_thread_start (args=0xc8e350) at /home/msyksphinz/work/riscv/qemu/util/qemu-thread-posix.c:519
#11 0x00007ffffe367164 in start_thread (arg=<optimized out>) at pthread_create.c:486
#12 0x00007ffffe28adef in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

うーん、これはtb_lookup__cpu_stateのところが怪しい気がする。ん?というか基本的にtlb_fill()が実装されていないということか?

static void myriscvx_cpu_class_init(ObjectClass *c, void *data)
{
    ...
  // cc->tlb_fill = myriscvx_cpu_tlb_fill;
}

コメントアウトしてた。cpu_helper.cに実装するらしい。いろいろ省略して、とりあえず物理アドレスしか使わないような実装にしてみる。

  • qemu/target/myriscvx/cpu_helper.c
bool myriscvx_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
                           MMUAccessType access_type, int mmu_idx,
                           bool probe, uintptr_t retaddr)
{
  MYRISCVXCPU *cpu = MYRISCVX_CPU(cs);
  CPUMYRISCVXState *env = &cpu->env;
...
   if (/* myriscvx_cpu_virt_enabled(env) || */m_mode_two_stage || hs_mode_two_stage) {
    /* Two stage lookup */
    ret = get_physical_address(env, &pa, &prot, address, access_type,
                               mmu_idx, true, true);
...
static int get_physical_address(CPUMYRISCVXState *env, hwaddr *physical,
                                int *prot, target_ulong addr,
                                int access_type, int mmu_idx,
                                bool first_stage, bool two_stage)
{
  *physical = addr;
  *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
  return TRANSLATE_SUCCESS;
}

これでビルドしてみたが、やはり途中で落ちる。まだ別の機能を実装していないからだな。調査を続ける。