开源灵车,创!
这个富有攻击性的仓库 中包含一个
(WIP 的)RISC-V 64 内核。代码写不动的时候就会想到去借鉴一下别人的逻辑,
在 GitHub 以 riscv 和 kernel 为关键词进行搜索,排名靠前的项目之一就是
xv6-riscv。这个仓库在 QEMU 虚拟化
的 RISC-V 系统上复刻了经典的 UNIX v6。
git clone
,看代码。
If no PMP entry matches an M-mode access, the access succeeds. If no PMP entry matches an S-mode or U-mode access, but at least one PMP entry is implemented, the access fails.
If at least one PMP entry is implemented, but all PMP entries’ A fields are set to OFF, then all S-mode and U-mode memory accesses will fail.
标准这么说。
mret 之后 QEMU 炸出不合法的指令 QEMU 卡死 QEMU v6 在执行 mret 后卡死 mret 切换到 S 模式时 QEMU 挂起
呃,一个很怪的问题。
根据上面给出的 issue, 问题集中出现在 QEMU v6.0.0 的更新上,所以就此着手查找问题 。
从 QEMU GitHub 镜像仓获得代码,我用了 treeless 模式来减少克隆时间(由于要在提交 树内回溯,不能 shallow clone)
git clone https://github.com/qemu/qemu.git --filter=blob:none
此时只有在检出时才会同步对应的对象文件。
QEMU 为每一个 release 都打了 tag,我们大体了解一下代码结构,能发现
/ -
|- target
| --- riscv
riscv 目录下包含了 RISC-V 的具体实现。以 RISCV_EXCP_ILLEGAL_INST 为关键词
grep 各源代码(这个关键词怎么找到的?根据 Issue 对应的异常是 Illegal
instruction,以 illegal 为关键词进行 case-insensitive grep 很容易在
cpu_bits.h
发现对应的定义),发现 mret_helper() 函数中确实在 v5.0.0 和
v6.0.0 中有较大差别。
v5.0.0:
target_ulong helper_mret(CPURISCVState *env, target_ulong cpu_pc_deb)
{
if (!(env->priv >= PRV_M)) // 鉴权
riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
target_ulong retpc = env->mepc; // 返回地址
if (!riscv_has_ext(env, RVC) && (retpc & 0x3)) // 检查地址对齐
riscv_raise_exception(env, RISCV_EXCP_INST_ADDR_MIS, GETPC());
target_ulong mstatus = env->mstatus;
target_ulong prev_priv = get_field(mstatus, MSTATUS_MPP);
target_ulong prev_virt = MSTATUS_MPV_ISSET(env);
mstatus = set_field(mstatus,
env->priv_ver >= PRIV_VERSION_1_10_0 ?
MSTATUS_MIE : MSTATUS_UIE << prev_priv,
get_field(mstatus, MSTATUS_MPIE));
mstatus = set_field(mstatus, MSTATUS_MPIE, 1);
mstatus = set_field(mstatus, MSTATUS_MPP, PRV_U);
mstatus = set_field(mstatus, MSTATUS_MPV, 0);
env->mstatus = mstatus;
riscv_cpu_set_mode(env, prev_priv);
if (riscv_has_ext(env, RVH)) {
if (prev_virt) {
riscv_cpu_swap_hypervisor_regs(env);
}
riscv_cpu_set_virt_enabled(env, prev_virt);
}
return retpc;
}
可以看到,v5.0.0 此函数只会在鉴权失败时抛出 Illegal Instruction。
v6.0.0:
target_ulong helper_mret(CPURISCVState *env, target_ulong cpu_pc_deb)
{
if (!(env->priv >= PRV_M)) { // 鉴权
riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
}
target_ulong retpc = env->mepc;
if (!riscv_has_ext(env, RVC) && (retpc & 0x3)) { // 检查对齐
riscv_raise_exception(env, RISCV_EXCP_INST_ADDR_MIS, GETPC());
}
uint64_t mstatus = env->mstatus;
target_ulong prev_priv = get_field(mstatus, MSTATUS_MPP);
if (!pmp_get_num_rules(env) && (prev_priv != PRV_M)) { // 这里!
riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
}
target_ulong prev_virt = get_field(env->mstatus, MSTATUS_MPV);
mstatus = set_field(mstatus, MSTATUS_MIE,
get_field(mstatus, MSTATUS_MPIE));
mstatus = set_field(mstatus, MSTATUS_MPIE, 1);
mstatus = set_field(mstatus, MSTATUS_MPP, PRV_U);
mstatus = set_field(mstatus, MSTATUS_MPV, 0);
env->mstatus = mstatus;
riscv_cpu_set_mode(env, prev_priv);
if (riscv_has_ext(env, RVH)) {
if (prev_virt) {
riscv_cpu_swap_hypervisor_regs(env);
}
riscv_cpu_set_virt_enabled(env, prev_virt);
}
return retpc;
}
从函数名字容易推断 pmp_get_num_rules()
能够获取已经定义的 PMP 规则的数量
(实际定义于 pmp.c
at line 74),即在 v6.0.0 版本的 QEMU 中会检查 PMP
规则的数量,一旦为零(没有任何规则被定义)且 mret 将会切换到的模式不为机器
模式,就会引发 Illegal Instruction 异常。
If at least one PMP entry is implemented, but all PMP entries’ A fields are set to OFF, then all S-mode and U-mode memory accesses will fail.
于是我成功避坑了,自己的 cocox-rv64
内核没有掉进这个问题虽然掉进了另一个问题。
直到群友的出现,一个叫做 switch_to
的文件,一个 mcause = 0x01 的异常一样挂死
了 QEMU,我立马想到 Issue 中的问题,给出了设置 PMP 寄存器的解决方案。
csrw pmpcfg0, 0xf
li t0, 0x3fffffffffffff
csrw pmpaddr0, t0
问题解决了。
但是新的问题出现了:为什么这时候 mcause 变成了 0x1(Instruction Access Fault)而非原来的 Illegal Instruction 了呢?
继续从代码中找问题:
这次我直接在QEMU Maillist 的 Archive 里查找 mret 关键词(先前 STFW 时有注意到相关邮件),直接抓到了更改的发生 原因:
修复 QEMU 行为的 PATCH 的提交者发现文档中指定的异常不是非法指令,而是访存错误;从此真相大白。
Patch 见此:
Fixes: d102f19a2085 ("target/riscv/pmp: Raise exception if no PMP entry is
configured")
Signed-off-by: Bin Meng <bmeng@tinylab.org>
---
target/riscv/op_helper.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c
index 09f1f5185d..d7af7f056b 100644
--- a/target/riscv/op_helper.c
+++ b/target/riscv/op_helper.c
@@ -202,7 +202,7 @@ target_ulong helper_mret(CPURISCVState *env)
if (riscv_feature(env, RISCV_FEATURE_PMP) &&
!pmp_get_num_rules(env) && (prev_priv != PRV_M)) {
- riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
+ riscv_raise_exception(env, RISCV_EXCP_INST_ACCESS_FAULT, GETPC());
}
target_ulong prev_virt = get_field(env->mstatus, MSTATUS_MPV);
--
2.34.1