QEMU裸机开发之M模式切换到S模式
从m模式切换到s模式下的代码其实也不是很多,主要是设置"mstatus"和“mepc”寄存器。其实从高特权级别切换到低特权级的思路在各个架构下都差不多,从低级别到高级别只有一种方式那就是产生了中断或者异常,从而进入高级别进行中断和异常处理,然后在中断中再返回到低级别。那么第一次从高级别切换到低级别的思路就很明显了,我们伪造出一个中断环境,设置好相关寄存器,最后执行中断返回指令就可以切换到低级别了,不同操作系统在不同架构下第一次切换到低权限级别思路基本都类似。
RISCV架构下的m切换到s模式添加的代码如下所示。
static void main(void) { printf("%s %d.\r\n", __func__, __LINE__); while(1); } static void machine_switchto_supervisor(void) { // set M Previous Privilege mode to Supervisor, for mret. unsigned long x = mstatus_get(); x &= ~MSTATUS_MPP_MASK; x |= MSTATUS_MPP_S; mstatus_set(x); // set M Exception Program Counter to main, for mret. // requires gcc -mcmodel=medany mepc_set((unsigned long)main); // disable paging for now. satp_set(0); // delegate interrupts and exceptions to supervisor mode. medeleg_set(0xb109); mideleg_set(0x222); // switch to supervisor mode and jump to main(). asm volatile("mret"); } void start(void) { printf("%s %d.\r\n", __func__, __LINE__); machine_trap_init(); //msoftint_make(); //timer_set(timer_get() + TIMER_CLK_RATE); machine_switchto_supervisor(); }
- 将“mstatus”中的MPP位域设置为s模式,这样当执行“mret”指令后就可以切换到s模式。
- 将“mepc”设置为main函数的地址,表示,切换到s模式时执行main函数的代码。
- 通过“satp_set”先将mmu关闭,防止回到s模式时由于意外打开mmu而页表并没有设置好导致崩溃。
- “medeleg_set”和“mideleg_set”将m模式的一些中断和异常托管给s模式处理,这个我们在后面s模式中断测试时就可以看到,这里两个值参考OpenSBI中实际设的值进行设置。
- 设置完后,直接通过“mret”指令就可以跳到main函数执行,同时切换到s模式。在之前的测试中发现,QEMU6.x版本在这里执行mret指令会崩溃,原因不明,而5.1和5.2版本都是正常的。
在命令行执行“make qemu”,可以看到切换到s模式下main函数中的打印信息,如下所示。
gewenbin@gewenbin-virtual-machine:~/Desktop/qemu_test/lesson7$ make qemu riscv64-unknown-elf-gcc -c -o entry.o entry.S riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -c -o start.o start.c riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -c -o uart.o uart.c riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -c -o trap.o trap.c riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -c -o clint.o clint.c riscv64-unknown-elf-ld -z max-page-size=4096 -T kernel.ld -o kernelimage entry.o start.o uart.o trap.o clint.o riscv64-unknown-elf-objdump -S kernelimage > kernel.asm riscv64-unknown-elf-objdump -t kernelimage | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel.sym qemu-system-riscv64 -machine virt -bios none -kernel kernelimage -m 128M -smp 1 -nographic start 58. main 28.
工程源码:链接:https://pan.baidu.com/s/1TnTYr7mywdKj5bxpdmWnyA,提取码:q772,见lesson7。
2022年8月22日 00:51 1F
兄弟,我开发板mret切换模式的崩溃原因是没设pmp
pmpcfg0 设为0x1f
pmpaddr0 设为-1
这样看看
2022年9月2日 21:58 B1
@ zz 这段代码参考的xv6源码,最新的xv6源码里加了pmp的设置,以前老的代码没有,没加可能在qemu里也能跑起来,在实机上可能就必须要设置了吧