1 背景介绍
在使用NEMU与QEMU做DiffTest的场景下,运行的固件为《RISC-V体系结构编程与实践》中示例代码chapter_2,编译出来的固件二进制文件为:benos_payload.bin
。
将benos_payload.bin,分别放置在NEMU与QEMU中的内存0x80000000处,并计划两者同时从0x80000000开始运行,期间运行到0x800000a8处(mret指令)时,下一条指令,预期均会跳转到0x80200000处,继续取指执行。
2 问题描述
NEMU执行mret指令后,PC可正常跳转到0x80200000处;
而QEMU执行mret指令后,却无法跳转到0x80200000处,QEMU直接跳转到了0x80200100处(f8000793 addi a5,zero,-128
),因此NEMU与QEMU中PC值不等,造成DiffTest提示错误。
NEMU与QEMU都从0x80000000开始运行benos_payload.bin,得到的日志:
0x80000000: 0x1117
0x80000004: 0x00010113
0x80000008: 0x12b7
0x8000000c: 0x00510133
0x80000010: 0x0040006f
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000014: 13 01 01 fc c_addi sp,-64,sp
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000018: 23 3c 81 02 sd 56(sp),s0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000001c: f3 27 00 30 csrrs $0,0x300,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000020: 13 84 07 00 c_mv a5,s0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000024: 93 07 04 00 c_mv s0,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000028: 23 34 f1 02 sd 40(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000002c: 03 37 81 02 ld 40(sp),a4
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000030: b7 e7 ff ff lui 0xffffffffffffe000,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000034: 93 87 f7 7f c_addi a5,2047,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000038: 33 77 f7 00 c_and a4,a5,a4
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000003c: b7 17 00 00 lui 0x1000,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000040: 93 87 07 80 c_addi a5,-2048,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000044: b3 67 f7 00 or a4,a5,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000048: 23 34 f1 02 sd 40(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000004c: 83 37 81 02 ld 40(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000050: 93 f7 f7 f7 c_andi a5,-129,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000054: 23 34 f1 02 sd 40(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000058: 83 37 81 02 ld 40(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000005c: 23 30 f1 02 sd 32(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000060: 83 37 01 02 ld 32(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000064: 73 90 07 30 csrrw a5,0x300,$0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000068: 93 07 10 40 c_li $0,1025,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000006c: 93 97 57 01 c_slli a5,21,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000070: 23 3c f1 00 sd 24(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000074: 83 37 81 01 ld 24(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000078: 73 90 17 34 csrrw a5,0x341,$0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000007c: 93 07 10 40 c_li $0,1025,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000080: 93 97 57 01 c_slli a5,21,a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000084: 23 38 f1 00 sd 16(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000088: 83 37 01 01 ld 16(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000008c: 73 90 57 10 csrrw a5,0x105,$0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000090: 23 34 01 00 sd 8(sp),$0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000094: 83 37 81 00 ld 8(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x0000000080000098: 73 90 47 10 csrrw a5,0x104,$0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x000000008000009c: 23 30 01 00 sd 0(sp),$0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x00000000800000a0: 83 37 01 00 ld 0(sp),a5
[src/cpu/cpu-exec.c:578,fetch_decode] 0x00000000800000a4: 73 90 07 18 csrrw a5,0x180,$0
[src/cpu/cpu-exec.c:578,fetch_decode] 0x00000000800000a8: 73 00 20 30 mret
[/home/higon/huangchao/NEMU/NEMU/include/cpu/difftest.h:49,difftest_check_reg] pc is different after executing instruction at pc = 0x00000000800000a8, right = 0x0000000000000000, wrong = 0x0000000080200000
3 原因分析
对于低版本QEMU(4.2.1),似乎不检查PMP配置,因此可正常运行。
对于高版本QEMU(我用的7.1.0),会检查PMP配置,因此需要在SBI中初始化PMP才能运行。
书作者的声明,参考链接:
https://github.com/runninglinuxkernel/riscv_programming_practice/tree/master/chapter_2,打开链接中的必读.txt
查看。
至于PMP配置的具体原理,可以去翻翻spec和文末的代码。
4 解决办法
在chapter_2/benos/sbi/sbi_main.c的sbi_main函数中,添加2行sbi_set_pmp
函数调用,如下所示:
/*
* 运行在M模式
*/
void sbi_main(void)
{
unsigned long val;
/*
* 配置PMP
* 所有地址空间都可以访问
*/
sbi_set_pmp(0, 0, -1UL, PMP_RWX);
sbi_set_pmp(1, 0x80000000, 0x40000, PMP_RWX);
/* 设置跳转模式为S模式 */
val = read_csr(mstatus);
val = INSERT_FIELD(val, MSTATUS_MPP, PRV_S);
val = INSERT_FIELD(val, MSTATUS_MPIE, 0);
write_csr(mstatus, val);
/* 设置M模式的Exception Program Counter,用于mret跳转 */
write_csr(mepc, FW_JUMP_ADDR);
/* 设置S模式异常向量表入口*/
write_csr(stvec, FW_JUMP_ADDR);
/* 关闭S模式的中断*/
write_csr(sie, 0);
/* 关闭S模式的页表转换 */
write_csr(satp, 0);
/* 切换到S模式 */
asm volatile("mret");
}
当然还有其他一些修改,都比较简单,修改好的代码,可从这里获取:
https://gitee.com/bailiyang/cdemo/tree/master/riscv_programming_practice/chapter_2/benos
参考文档: