自作Binary Translation型RISC-Vエミュレータの開発、いい加減本命のTCG Block Chainingを実装しろと言いたいところだが、まだ本来のテストパタンがPASSできなくて地味にデバッグを続けていた。
一つ問題になったのは、本当に時々エミュレーション中にSegmentation Faultで落ちてしまうときがある。Dhrystone実行中にも頻繁に発生し始めたので何故だろうこれはと思い解析してみた。
どうもTBのEXIT条件がおかしくなっているっぽい。
00007F1D789402AA 488B5510 mov 0x10(%rbp),%rdx 00007F1D789402AE 488B9510000000 mov 0x10(%rbp),%rdx 00007F1D789402B5 488B5D08 mov 8(%rbp),%rbx 00007F1D789402B9 488B9D08000000 mov 8(%rbp),%rbx 00007F1D789402C0 488BC2 mov %rdx,%rax 00007F1D789402C3 480500000000 add $0,%rax 00007F1D789402C9 48898508020000 mov %rax,0x208(%rbp) 00007F1D789402D0 E93AFDD678 jmp 0x0000_7F1D_F16B_000F ========= BLOCK START ========= 9550: Guest PC Address = 800012c8 00000000800012c8:00000000800012c8 Hostcode 15e30405 : c.addi s0, 1 00000000800012ca:00000000800012ca Hostcode ff4515e3 : bne a0, s4, pc + 4074 label found 2 label found. offset = 4e replacement target is 34, data = 16 00007F1D6D850000 488B5548 mov 0x48(%rbp),%rdx 00007F1D6D850004 488B9548000000 mov 0x48(%rbp),%rdx 00007F1D6D85000B 4881C201000000 add $1,%rdx 00007F1D6D850012 48895548 mov %rdx,0x48(%rbp) 00007F1D6D850016 488B5558 mov 0x58(%rbp),%rdx 00007F1D6D85001A 488B9558000000 mov 0x58(%rbp),%rdx 00007F1D6D850021 488B9DA8000000 mov 0xA8(%rbp),%rbx 00007F1D6D850028 488B9DA8000000 mov 0xA8(%rbp),%rbx 00007F1D6D85002F 483BD3 cmp %rbx,%rdx 00007F1D6D850032 0F8516000000 jne 0x0000_7F1D_6D85_004E 00007F1D6D850038 48B8CE12008000000000 movabs $0x8000_12CE,%rax 00007F1D6D850042 48898508020000 mov %rax,0x208(%rbp) 00007F1D6D850049 E9C1FFE583 jmp 0x0000_7F1C_F16B_000F 00007F1D6D85004E 48B8B412008000000000 movabs $0x8000_12B4,%rax 00007F1D6D850058 48898508020000 mov %rax,0x208(%rbp) 00007F1D6D85005F E9ABFFE583 jmp 0x0000_7F1C_F16B_000F zsh: segmentation fault (core dumped) cargo run --release -- --debug --dump-host --dump-guest --machine sifive_u
あれ、最後のJMPのアドレスがずれている気がする。最初は0x0000_7F1D_F16B_000F
なのに、0x0000_7F1C_F16B_000F
に変わっている。これはなんでだ?
よく見てみると、これはジャンプに必要な相対距離が32ビットの範囲を超えてしまっているために発生しているっぽい。確かにジャンプの相対距離が32ビットに収まるかどうかはについては確認していなかった。これはTCGの生成方法に問題があるので、ジャンプの方式を変えて、相対アドレスではなく絶対アドレスでジャンプするように変更した。
00007F51919B020B 48FFE0 jmp *%rax 00007F51919B020E 48B8DA11008000000000 movabs $0x8000_11DA,%rax 00007F51919B0218 48898508020000 mov %rax,0x208(%rbp) 00007F51919B021F 48B80F00D8E6517F0000 movabs $0x7F51_E6D8_000F,%rax 00007F51919B0229 48FFE0 jmp *%rax 00007F51919B022C 48B80F00D8E6517F0000 movabs $0x7F51_E6D8_000F,%rax 00007F51919B0236 48FFE0 jmp *%rax ========= BLOCK START ========= 6360: Guest PC Address = 800011da 00000000800011da:00000000800011da Hostcode e70ce30c : c.sd a1, 0(a4) 00000000800011dc:00000000800011dc Hostcode 0741e70c : c.sd a1, 8(a4) 00000000800011de:00000000800011de Hostcode 6de30741 : c.addi a4, 16 00000000800011e0:00000000800011e0 Hostcode fed76de3 : bltu a4, a3, pc + 4090 ...
一度絶対アドレスを生成して%RAX
に格納して、レジスタジャンプを行う。これで一応Segmentation Faultは出なくなった。
QEMUの終了条件
ちなみに、QEMUの終了条件についても調査した。複数スレッドが立ち上がっている状態でどのようにモニタスレッドがすべてのスレッドを停止させるのか監視したかった。
が、qemu-system-riscv64
を見ていると、--machine=virt
の場合はTESTデバイスにアクセスするとすぐに強制的に終了している。これでいいのかなあ...
qemu-5.1.0/hw/riscv/sifive_test.c
static void sifive_test_write(void *opaque, hwaddr addr, uint64_t val64, unsigned int size) { if (addr == 0) { int status = val64 & 0xffff; int code = (val64 >> 16) & 0xffff; switch (status) { case FINISHER_FAIL: exit(code); case FINISHER_PASS: exit(0); case FINISHER_RESET: qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); return; default: break; } } qemu_log_mask(LOG_GUEST_ERROR, "%s: write: addr=0x%x val=0x%016" PRIx64 "\n", __func__, (int)addr, val64); }