- 阅读实验指导零和一,最好一步步跟着实现一遍。
- checkout 到仓库中的
lab-1
分支,实验题将以此展开。
我们的实验题会提供一个基础的代码框架,以便于进行实验。如果你选择参考教程,自己编写操作系统,这个代码框架也可以用来进行对照。
-
原理:在
rust_main
函数中,执行ebreak
命令后至函数结束前,sp
寄存器的值是怎样变化的?{% reveal %}
sp
首先减去一个Context
的大小(入栈),然后原sp
的值被保存到这个入栈的Context
中。执行
handle_interrupt
的过程中,随着局部变量的使用,编译器可能会自动加入一些出入栈操作。但无论如何,handle_interrupt
前后sp
的值是一样的。从
handle_interrupt
返回后,执行__restore
,在最后将保存的原sp
值恢复。 {% endreveal %}
<br>
-
分析:如果去掉
rust_main
后的panic
会发生什么,为什么?{% reveal %}
rust_main
返回后,程序并没有停止。rust_main
是在entry.asm
中通过jal
指令调用的,因此其执行完后会回到entry.asm
中。但是,entry.asm
并没有在后面写任何指令,这意味着程序将接着向后执行内存中的任何指令。我们可以通过
rust-objdump -d -S os/target/riscv64imac-unknown-none-elf/debug/os | less
来查看汇编代码,其中就能看到:_start
只有短短三条指令,而后面则放着许多 Rust 库中的函数。这些指令可能导致程序进入循环,或崩溃退出。 {% endreveal %}
<br>
- 实验
-
如果程序访问不存在的地址,会得到
Exception::LoadFault
。模仿捕获ebreak
和时钟中断的方法,捕获LoadFault
(之后panic
即可)。{% reveal %}
-
直接在
match
中添加一个 arm 即可。例如Trap::Exception(Exception::LoadFault) => panic!()
{% endreveal %}
<br>
2. 在处理异常的过程中,如果程序想要非法访问的地址是 `0x0`,则打印 `SUCCESS!`。
{% reveal %}
如果程序因无效访问内存造成异常,这个访问的地址会被存放在
stval
中,而它已经被我们作为参数传入handle_interrupt
了,因此直接判断即可 {% endreveal %}
<br>
3. 添加或修改少量代码,使得运行时触发这个异常,并且打印出 `SUCCESS!`。
- 要求:不允许添加或修改任何 unsafe 代码
<br>
{% reveal %}
- 解法 1:在
interrupt/handler.rs
的breakpoint
函数中,将context.sepc += 2
修改为context.sepc = 0
(则sret
时程序会跳转到0x0
)- 解法 2:去除
rust_main
中的panic
语句,并在entry.asm
的jal rust_main
之后,添加一行读取0x0
地址的指令(例如jr x0
或ld x1, (x0)
)
{% endreveal %}