nemu-riscv 特权指令杂谈

做PA的时候忽略了讲义上的一段话,导致做PA的时候苦苦调试了好两天的nemu/Spike(www)

如果你仔细RTFM, 你会发现标准RISC-V的分页机制需要在S模式及U模式下才能开启, 而在M模式下的访存并不会进行MMU的地址转换. 但我们在NEMU中进行了简化, 允许M模式的访存也进行地址转换, 这样可以避免引入S模式相关的细节, 让大家把注意力集中在分页机制本身.

但是, 既然踩了很久的坑, 那么还是写一下吧

mstatus寄存器

讲义上说可以把mstatus设置成0x1800(pass difftest),但实际上仔细研读手册/spike代码发现mstatus的初始值应该是0

ecall/mret行为:

ecall的时候保存当前状态进mstatus.MPP;设置MIE
mret从mstatus.MPP恢复当前状态并清除位

嵌套中断?

有几个比较重要的位:

MPP (Machine Previous Privilege) (11:12)

保存进入异常前的特权级别

00:用户模式(U-mode)
01:监督模式(S-mode)
11:机器模式(M-mode)

这里是保存了previous_privilege, 也就是执行ecall时候的特权记别, 之前以为他保存了当前的特权基本导致调试了很久

当前的特权级应该不是由csr/通用寄存器来存储的,应该是硬件内部的行为->看见spike的源代码里面获取特权级是reg_t mode = proc->state.prv;, 并不是通用寄存器

MPRV(Modify PRiVilege)

这个位会修改加载和存储操作的有效权限级别

  • 当 MPRV=0 时,加载和存储操作按照当前权限模式的翻译和保护机制执行,行为正常。
  • 当 MPRV=1 时,加载和存储的内存地址会被翻译和保护,且应用字节序,就好像当前权限模式被设置为 MPP(Machine Previous Privilege)一样。
  • 指令地址的翻译和保护不受 MPRV 设置的影响。
  • 如果不支持 U-mode,MPRV 是只读的 0。
  • 执行 MRET 或 SRET 指令且权限模式降级到比 M 模式更低时,MPRV 会被设置为 0。

SUM

怪怪的,手册上似乎没有说相关的内容

MIE(Global interrupt-enable bits)

M-Mode 的中断使能位

P.s. SIE是S-Mode的中断使能位

mcause

mcause 非常多, PA中能用到的就下面几个

  • 0xb
  • 0x8
  • timer-interrupt

虚拟内存

页表转换就不详细写了, 操作系统基础(^-^)

来聊一聊PTE的权限位把

1
RSW D A G U, X W R, V

V:valid,有效

X W R Meaning
0 0 0 Pointer to next level of page table.
0 1 1 Read-only page.
1 1 0 Reserved for future use.
0 1 1 Read-write page.
0 0 1 Execute-only page.
1 0 1 Read-execute page.
0 1 0 Reserved for future use.
1 0 1 Read-write-execute page.

U:User 用户权限能访问
G:Global mapping
A:Accessed 被访问过
D:Ditry 被写入过

emm, 这一段非常曲折, 我没有认真看讲义, 也没有很认真看rv手册, 调试了很久Spike后反回来看手册, 然后发现M-Mode是不会进行虚拟地址转换的!
对于S-Mod, 可以设置SUM位来允许M-Mod访问用户态的页表

一开始没看手册, debug调试到这里发现反回levels是0(页表极数为0),就不进行地址转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct vm_info {
int levels;
int idxbits;
int widenbits;
int ptesize;
reg_t ptbase;
};

inline vm_info decode_vm_info(int xlen, bool stage2, reg_t prv, reg_t satp)
{
if (prv == PRV_M) {
return {0, 0, 0, 0, 0};
} else if (!stage2 && prv <= PRV_S && xlen == 32) {
switch (get_field(satp, SATP32_MODE)) {
case SATP_MODE_OFF: return {0, 0, 0, 0, 0};
case SATP_MODE_SV32: return {2, 10, 0, 4, (satp & SATP32_PPN) << PGSHIFT};
default: abort();
}
...
}
}

一种(投机取巧)的difftest方案

由于nemu-spike没有设置对于csr的difftest,就有一个投机取巧的方案(感觉也不是很好)
就是逐个把csr寄存器都出来,然后就可以diff出问题了

1
2
3
void read_mstatus_to_a2() {
asm volatile ("csrr a2, mstatus" ::: "a2");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.macro check_csr
// for diff-test-state!
csrw sscratch ,sp
csrr sp, mstatus
csrr sp, sscratch
csrr sp, mtvec
csrr sp, mcause
#csrr sp, sstatus
csrr sp, mepc
//csrrw sp, mvendorid,sp
//csrrw sp, marchid, sp
csrr sp, satp
csrr sp, mscratch
csrr sp, sscratch
// check_end
.endm

关于gdb调试NEMU-Spike

spike的代码还是很庞大的,如果没有gdb调试很难找到关键位置

但是在gdb里面默认调试spike的时候是”NO SOURCE AVAILABLE”

尝试把编译选项加上了-g -O3后依旧没有用

检查编译的elf:

1
readelf --debug-dump=info xxxxx

发现有绝对路径,能正常调试

1
gdb info sharedlibrary

发现能正常查看共享库

1
info sources

发现(Objfile has no debug information.)
如果太长了想记录一下输出可以

1
set logging on

最后检查发现是有一个参数

1
-fvisibility=hidden

这个参数隐藏了符号


nemu-riscv 特权指令杂谈
https://20040702.xyz/2025/01/17/riscv-privilege/
作者
Seeker
发布于
2025年1月17日
许可协议