找回密码
 立即注册
首页 业界区 安全 [rCore学习笔记 020]第二章作业

[rCore学习笔记 020]第二章作业

寇秀娟 13 小时前
写在前面

本随笔是非常菜的菜鸡写的。如有问题请及时提出。
可以联系:1160712160@qq.com
GitHhub:https://github.com/WindDevil (目前啥也没有
编程题

实现一个裸机应用程序A,能打印调用栈

首先在这里卡了我很久的是调用栈保存在哪里,回想到上一部分画的图,其实就在.bss段后边.
这里注意sp寄存器是栈指针寄存器,fp寄存器是帧指针寄存器,是不一样的.
这里重提一下os/.cargo/config的内容,"-Cforce-frame-pointers=yes"代表保存栈指针:
  1. # os/.cargo/config
  2. [build]
  3. target = "riscv64gc-unknown-none-elf"
  4. [target.riscv64gc-unknown-none-elf]
  5. rustflags = [
  6.      "-Clink-arg=-Tsrc/linker-qemu.ld", "-Cforce-frame-pointers=yes"
  7. ]
复制代码
此时可以通过fp的值(保证它不在trap过程中在用户栈或者内核栈中).
这时候可以创建os/src/stack_trace.rs:
  1. use core::{arch::asm, ptr};
  2. pub unsafe fn print_stack_trace() -> () {
  3.     let mut fp: *const usize;
  4.     asm!("mv {}, fp", out(reg) fp);
  5.     println!("== Begin stack trace ==");
  6.     while fp != ptr::null() {
  7.         let saved_ra = *fp.sub(1);
  8.         let saved_fp = *fp.sub(2);
  9.         println!("0x{:016x}, fp = 0x{:016x}", saved_ra, saved_fp);
  10.         fp = saved_fp as *const usize;
  11.     }
  12.     println!("== End stack trace ==");
  13. }
复制代码
在main.rs里把它加入main.rs作为一个子模块:
  1. mod stack_trace;
复制代码
编辑lang_items.rs,在panic函数里加入unsafe { print_stack_trace(); }.
注意添加上依赖use crate::stack_trace::print_stack_trace;
在AppManager的函数load_app的所有APP运行完毕的部分加上panic!("Shutdown machine!");,代替原来的正常关机.
  1. impl AppManager {
  2.     pub fn print_app_info(&self) {
  3.         println!("[kernel] num_app = {}", self.num_app);
  4.         for i in 0..self.num_app {
  5.             println!(
  6.                 "[kernel] app_{} [{:#x}, {:#x})",
  7.                 i,
  8.                 self.app_start[i],
  9.                 self.app_start[i + 1]
  10.             );
  11.         }
  12.     }
  13.     unsafe fn load_app(&self, app_id: usize) {
  14.         if app_id >= self.num_app {
  15.             println!("All applications completed!");
  16.             panic!("Shutdown machine!");
  17.             //shutdown(false);
  18.         }
  19.         println!("[kernel] Loading app_{}", app_id);
  20.         // clear app area
  21.         core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
  22.         let app_src = core::slice::from_raw_parts(
  23.             self.app_start[app_id] as *const u8,
  24.             self.app_start[app_id + 1] - self.app_start[app_id],
  25.         );
  26.         let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
  27.         app_dst.copy_from_slice(app_src);
  28.         // Memory fence about fetching the instruction memory
  29.         // It is guaranteed that a subsequent instruction fetch must
  30.         // observes all previous writes to the instruction memory.
  31.         // Therefore, fence.i must be executed after we have loaded
  32.         // the code of the next app into the instruction memory.
  33.         // See also: riscv non-priv spec chapter 3, 'Zifencei' extension.
  34.         asm!("fence.i");
  35.     }
  36.     pub fn get_current_app(&self) -> usize {
  37.         self.current_app
  38.     }
  39.     pub fn move_to_next_app(&mut self) {
  40.         self.current_app += 1;
  41.     }
  42. }
复制代码
然后直接在os目录下make run.如果报错了
  1. error: unused import: `crate::sbi::shutdown`
  2.   --> src/batch.rs:3:5
  3.    |
  4. 3  | use crate::sbi::shutdown;
  5.    |     ^^^^^^^^^^^^^^^^^^^^
  6.    |
复制代码
直接注释掉use crate::sbi::shutdown;即可.
这样就可以得到结果:
  1. [rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
  2. .______       __    __      _______.___________.  _______..______   __
  3. |   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
  4. |  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
  5. |      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
  6. |  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
  7. | _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
  8. [rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
  9. [rustsbi] Platform Name      : riscv-virtio,qemu
  10. [rustsbi] Platform SMP       : 1
  11. [rustsbi] Platform Memory    : 0x80000000..0x88000000
  12. [rustsbi] Boot HART          : 0
  13. [rustsbi] Device Tree Region : 0x87000000..0x87000f02
  14. [rustsbi] Firmware Address   : 0x80000000
  15. [rustsbi] Supervisor Address : 0x80200000
  16. [rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
  17. [rustsbi] pmp02: 0x80000000..0x80200000 (---)
  18. [rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
  19. [rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
  20. [kernel] Hello, world!
  21. [kernel] num_app = 5
  22. [kernel] app_0 [0x8020a038, 0x8020b360)
  23. [kernel] app_1 [0x8020b360, 0x8020c730)
  24. [kernel] app_2 [0x8020c730, 0x8020dcd8)
  25. [kernel] app_3 [0x8020dcd8, 0x8020f090)
  26. [kernel] app_4 [0x8020f090, 0x80210440)
  27. [kernel] Loading app_0
  28. Hello, world!
  29. [kernel] Application exited with code 0
  30. [kernel] Loading app_1
  31. Into Test store_fault, we will insert an invalid store operation...
  32. Kernel should kill this application!
  33. [kernel] PageFault in application, kernel killed it.
  34. [kernel] Loading app_2
  35. 3^10000=5079(MOD 10007)
  36. 3^20000=8202(MOD 10007)
  37. 3^30000=8824(MOD 10007)
  38. 3^40000=5750(MOD 10007)
  39. 3^50000=3824(MOD 10007)
  40. 3^60000=8516(MOD 10007)
  41. 3^70000=2510(MOD 10007)
  42. 3^80000=9379(MOD 10007)
  43. 3^90000=2621(MOD 10007)
  44. 3^100000=2749(MOD 10007)
  45. Test power OK!
  46. [kernel] Application exited with code 0
  47. [kernel] Loading app_3
  48. Try to execute privileged instruction in U Mode
  49. Kernel should kill this application!
  50. [kernel] IllegalInstruction in application, kernel killed it.
  51. [kernel] Loading app_4
  52. Try to access privileged CSR in U Mode
  53. Kernel should kill this application!
  54. [kernel] IllegalInstruction in application, kernel killed it.
  55. All applications completed!
  56. == Begin stack trace ==
  57. 0x0000000080201244, fp = 0x0000000080206cf0
  58. 0x0000000080201a78, fp = 0x0000000080206d30
  59. 0x0000000080200ed8, fp = 0x0000000080206da0
  60. 0x00000000802010bc, fp = 0x0000000080206e00
  61. 0x0000000080201724, fp = 0x0000000080206ef0
  62. 0x0000000080200a70, fp = 0x0000000080208fc0
  63. 0x0000000080400032, fp = 0x0000000080209000
  64. 0x0000000000000000, fp = 0x0000000000000000
  65. == End stack trace ==
  66. make: *** [Makefile:64: run-inner] Error 255
复制代码
后续题目


问答题

1. 函数调用与系统调用有何区别?


  • 函数调用用普通的控制流指令,不涉及特权级的切换;系统调用使用专门的指令(如 RISC-V 上的 ecall),会切换到内核特权级。
  • 函数调用可以随意指定调用目标;系统调用只能将控制流切换给调用操作系统内核给定的目标。
这里注意去看之前提到的trap处理流程:

  • print! -> print -> Stdout.write_fmt -> write -> sys_write -> syscall 是在user层的函数调用链.
  • syscall -> sys_write -> print! -> Stdout.write_fmt -> console_putchar 是在os层的函数调用链.
  • 使用x10~x17作为其中的桥梁
2. 为了方便操作系统处理,M态软件会将 S 态异常/中断委托给 S 态软件,请指出有哪些寄存器记录了委托信息,rustsbi 委托了哪些异常/中断?(也可以直接给出寄存器的值)

这个问题主要是在rust-sbi之中,这个题当然可以通过直接问GPT得出答案,但是为了增强我们的查询能力,假如GPT不知道或者我们需要更多的更准确更标准的信息,我们可以查看RISC-V手册 (ustc.edu.cn),寻找其中内容.
我们在其中搜索委托,可以看到 10.5 现代操作系统的监管者模式 这一章中提到了关于RISC-V的委托机制的描述.
默认情况下,发生所有异常(不论在什么权限模式下)的时候,控制权都会被移交到 M 模式的异常处理程序。但是 Unix 系统中的大多数例外都应该进行 S 模式下的系统调 用。M 模式的异常处理程序可以将异常重新导向 S 模式,但这些额外的操作会减慢大多数 异常的处理速度。因此,RISC-V 提供了一种异常委托机制。通过该机制可以选择性地将中 断和同步异常交给 S 模式处理,而完全绕过 M 模式。
这里提到两个CSR,mideleg和medeleg,分别可以把中断和同步异常委托给S模式:
mideleg(Machine Interrupt Delegation,机器中断委托)CSR 控制将哪些中断委托给 S 模式。
M 模式还可以通过 medeleg CSR 将同步异常委托给 S 模式。
这里同步异常的名字可能比较奇怪,容易让人想到还有别的类型的异常,但是实际上异常只有两类:同步异常,中断:
RISC-V 将 异常分为两类。一类是同步异常,这类异常在指令执行期间产生,如访问了无效的存储器 地址或执行了具有无效操作码的指令时。另一类是中断,它是与指令流异步的外部事件, 比如鼠标的单击。
请注意,无论委派设置是怎样的,发生异常时控制权都不会移交给权限更低的模式。 在 M 模式下发生的异常总是在 M 模式下处理。在 S 模式下发生的异常,根据具体的委派 设置,可能由 M 模式或 S 模式处理,但永远不会由 U 模式处理。
图中含有关于中断和同步异常的描述,前半个表格是关于 中断 的后半个表格是关于 同步异常 的.把对应的位设置为高就可以把对应异常委托给S模式.
例如, mideleg[5]对应于 S 模式的时钟中断,如果把它置位,S 模式的时钟中断将会移交 S 模式 的异常处理程序,而不是 M 模式的异常处理程序。
例如,置上 medeleg[15]便会把 store page fault(store 过程中出现的缺页)委托给 S 模式。
2.png

使用make debug进入debug模式,使用GDB命令break rust_main打断点,然后使用c命令使得程序停在rust_main之前.
使用指令info register mideleg和info register medeleg可以读取到两个寄存器的值:
  1. mideleg        0x666    1638
  2. medeleg        0xf0b5ff 15775231
复制代码
这里发现和答案对应不上,对应不上就对了,看上图,实际上mideleg的有效位数为奇数位,应该让0x666 按位与 一个b1010 1010 1010,得到的就是0x222了.
同理对于medeleg的0xf0b5ff应该取其0~15位缺10和14位,应该 按位与 一个b1011 1011 1111 1111得到0xb1ff,这里似乎还算和答案不一致,但是我们可以自圆其说,先不管了.
3. 如果操作系统以应用程序库的形式存在,应用程序可以通过哪些方式破坏操作系统?

这题主要是对比第一章和第二章操作系统的不同,找到第一章操作系统的问题.
第一印象产生出来主要是我们第二章忙活的部分,也就是在于关于调用更高级别指令的问题.但是调用更高级别的指令,我们如果用操作系统来拦截这些指令,就可以保证我们的操作都是已经定义的,就不会出现问题了.
这题所有的缺点,反过来就是第二章所有的优点.答案已经写得很完美了.
如果操作系统以应用程序库的形式存在,那么编译器在链接OS库时会把应用程序跟OS库链接成一个可执行文件,两者处于同一地址空间,这也是LibOS(Unikernel)架构,此时存在如下几个破坏操作系统的方式:

  • 缓冲区溢出:应用程序可以覆盖写其合法内存边界之外的部分,这可能会危及 OS;
  • 整数溢出:当对整数值的运算产生的值超出整数数据类型可以表示的范围时,就会发生整数溢出, 这可能会导致OS出现意外行为和安全漏洞。 例如,如果允许应用程序分配大量内存,攻击者可能会在内存分配例程中触发整数溢出,从而可能导致缓冲区溢出或其他安全漏洞;
  • 系统调用拦截:应用程序可能会拦截或重定向系统调用,从而可能损害OS的行为。例如,攻击者可能会拦截读取敏感文件的系统调用并将其重定向到他们选择的文件,从而可能危及 unikernel 的安全性。
  • 资源耗尽:应用程序可能会消耗内存或网络带宽等资源,可能导致拒绝服务或其他安全漏洞。
4. 编译器/操作系统/处理器如何合作,可采用哪些方法来保护操作系统不受应用程序的破坏?

仍然是参考官方文档的参考答案.
硬件操作系统运行在一个硬件保护的安全执行环境中,不受到应用程序的破坏;应用程序运行在另外一个无法破坏操作系统的受限执行环境中。 现代CPU提供了很多硬件机制来保护操作系统免受恶意应用程序的破坏,包括如下几个:

  • 特权级模式:处理器能够设置不同安全等级的执行环境,即用户态执行环境和内核态特权级的执行环境。处理器在执行指令前会进行特权级安全检查,如果在用户态执行环境中执行内核态特权级指令,会产生异常阻止当前非法指令的执行。
  • TEE(可信执行环境):CPU的TEE能够构建一个可信的执行环境,用于抵御恶意软件或攻击,能够确保处理敏感数据的应用程序(例如移动银行和支付应用程序)的安全。
  • ASLR(地址空间布局随机化):ASLR 是CPU的一种随机化进程地址空间布局的安全功能,其能够随机生成进程地址空间,例如栈、共享库等关键部分的起始地址,使攻击者预测特定数据或代码的位置。
5. RISC-V处理器的S态特权指令有哪些,其大致含义是什么,有啥作用?

这个问题之前我们说过它的分类,而具体的S态特权指令则应该查询参考书目RISC-V手册 (ustc.edu.cn).
用户态软件为获得内核态操作系统的服务功能而执行特殊指令:
1. 指令本身属于高特权级的指令,如 sret 指令(表示从 S 模式返回到 U 模式)
2. 指令访问了 S模式特权级下才能访问的寄存器 或内存,如表示S模式系统状态的 控制状态寄存器 sstatus 等
通过查阅手册我们可以得出特权指令分为如下图所示:
3.png

sret的作用:
4.png

mret的作用:
5.png

wfi的作用:
6.png

sfence.vma的作用:
7.png

访问CSR的指令,这里直接列出M模式下CSR的列表,再S模式下的CSR命名则是把名字里的m,更换为s:
8.png

另外还有一些CSR专用的指令:
9.png

图中的CSR相关指令为:
10.png

6. RISC-V处理器在用户态执行特权指令后的硬件层面的处理过程是什么?

包括上部分画的图从来都是讲的怎么做特权级切换,但是我们总是忽略在切换上下文的时候的CSR的变化,这些部分想想就是通过硬件来改变的,这个问题刚好对我们是一个非常合适的提醒.
CPU 执行完一条指令(如 ecall )并准备从用户特权级 陷入( Trap )到 S 特权级的时候,硬件会自动完成如下这些事情:

  • sstatus 的 SPP 字段会被修改为 CPU 当前的特权级(U/S)。
  • sepc 会被修改为 Trap 处理完成后默认会执行的下一条指令的地址。
  • scause/stval 分别会被修改成这次 Trap 的原因以及相关的附加信息。
  • cpu 会跳转到 stvec 所设置的 Trap 处理入口地址,并将当前特权级设置为 S ,然后从Trap 处理入口地址处开始执行
CPU 完成 Trap 处理准备返回的时候,需要通过一条 S 特权级的特权指令 sret 来完成,这一条指令具体完成以下功能: * CPU 会将当前的特权级按照 sstatus 的 SPP 字段设置为 U 或者 S ; * CPU 会跳转到 sepc 寄存器指向的那条指令,然后继续执行。
7. 操作系统在完成用户态内核态双向切换中的一般处理过程是什么?

这个更详细的要看关于上一部分的绿色部分,关于ecall->x10~x17->trap_handler->sret这一部分的描述,可以看看(019 在main中测试本章实现)里的大图.
当 CPU 在用户态特权级( RISC-V 的 U 模式)运行应用程序,执行到 Trap,切换到内核态特权级( RISC-V的S 模式),批处理操作系统的对应代码响应 Trap,并执行系统调用服务,处理完毕后,从内核态返回到用户态应用程序继续执行后续指令。
8. 程序陷入内核的原因有中断、异常和陷入(系统调用),请问 riscv64 支持哪些中断 / 异常?如何判断进入内核是由于中断还是异常?描述陷入内核时的几个重要寄存器及其值。

这里就还是要看这张图了
11.png

要判断当前的中断和异常情况只需要看scause,RISC-V手册 (ustc.edu.cn) 对它的描述是:
scause 按图 10.3 根据异常类型设置,stval 被设置成出错的地址或者其它特定异常的信息字。
Trap时重要的寄存器也还是看这张图,这里直接列出M模式下CSR的列表,再S模式下的CSR命名则是把名字里的m,更换为s:
12.png

9. 在哪些情况下会出现特权级切换:用户态–>内核态,以及内核态–>用户态?

用户态到内核态的切换已经重复了很多次了:
上层软件执行过程中出现了一些异常或 特殊情况 , 需要用到执行环境中提供的功能
1. 这里可以看到虽然都叫做 异常 但是实际上有一部分情况是特殊情况需要使用执行环境中的功能,不能非黑即白地把 异常 理解为 坏的
2. 用户态应用直接触发从用户态到内核态的异常的原因总体上可以分为两种
1. 其一是用户态软件为获得内核态操作系统的服务功能而执行特殊指令
1. 指令本身属于高特权级的指令,如 sret 指令(表示从 S 模式返回到 U 模式)
2. 指令访问了 S模式特权级下才能访问的寄存器 或内存,如表示S模式系统状态的 控制状态寄存器 sstatus 等
2. 其二是在执行某条指令期间产生了错误(如执行了用户态不允许执行的指令或者其他错误)并被 CPU 检测到
但是从内核态到用户态,我们马上能想到的只有sret,但是想想我们这一章实现的操作系统是什么时候开始运行APP的呢,运行的时候是怎么进入用户态的呢,更具体更详细的似乎没有?
想想上一部分我们在官方手册中可以看到:
唯一一种能够使得 CPU 特权级下降的方法就是执行 Trap 返回的特权指令,如 sret 、mret 等
这时候我们再去RISC-V手册 (ustc.edu.cn) 寻找关于Trap返回的特权指令:
监管者异常返回指令 sret 与 mret 的行为相同,但它作用于 S 模式的异常处理 CSR,而不 是 M 模式的 CSR
可以看到Trap的返回指令只有sret和mret.
这里再重复一下它们俩的作用.
sret的作用:
13.png

mret的作用:
14.png

10. Trap上下文的含义是啥?在本章的操作系统中,Trap上下文的具体内容是啥?如果不进行Trap上下文的保存于恢复,会出现什么情况?

这个直接联系到我们对于TrapContext的定义代码:
  1. pub struct TrapContext {
  2.     /// general regs[0..31]
  3.     pub x: [usize; 32],
  4.     /// CSR sstatus      
  5.     pub sstatus: Sstatus,
  6.     /// CSR sepc
  7.     pub sepc: usize,
  8. }
复制代码
这里我们可以反过来回想到,Trap的上下文就是CSR保存的关于Trap的信息,以及通用寄存器.
如果不能进行Trap的上下文保存与恢复,CPU就不能正确恢复到原来的特权级.
实验练习

实践作业

sys_write 安全检查

ch2 中,我们实现了第一个系统调用 sys_write,这使得我们可以在用户态输出信息。但是 os 在提供服务的同时,还有保护 os 本身以及其他用户程序不受错误或者恶意程序破坏的功能。
由于还没有实现虚拟内存,我们可以在用户程序中指定一个属于其他程序字符串,并将它输出,这显然是不合理的,因此我们要对 sys_write 做检查:

  • sys_write 仅能输出位于程序本身内存空间内的数据,否则报错。
为AppManager创建一个获取当前APP地址的方法,并且因为每次加载APP之后current_app号加一,所以需要读取的范围是self.app_start[self.current_app]~self.app_start[self.current_app-1],计算出APP大小作为偏移量,然后通过基指针计算即可:
  1. impl AppManager {
  2.     pub fn print_app_info(&self) {
  3.         println!("[kernel] num_app = {}", self.num_app);
  4.         for i in 0..self.num_app {
  5.             println!(
  6.                 "[kernel] app_{} [{:#x}, {:#x})",
  7.                 i,
  8.                 self.app_start[i],
  9.                 self.app_start[i + 1]
  10.             );
  11.         }
  12.     }
  13.     unsafe fn load_app(&self, app_id: usize) {
  14.         if app_id >= self.num_app {
  15.             println!("All applications completed!");
  16.             //panic!("Shutdown machine!");
  17.             shutdown(false);
  18.         }
  19.         println!("[kernel] Loading app_{}", app_id);
  20.         // clear app area
  21.         core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
  22.         let app_src = core::slice::from_raw_parts(
  23.             self.app_start[app_id] as *const u8,
  24.             self.app_start[app_id + 1] - self.app_start[app_id],
  25.         );
  26.         let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
  27.         app_dst.copy_from_slice(app_src);
  28.         // Memory fence about fetching the instruction memory
  29.         // It is guaranteed that a subsequent instruction fetch must
  30.         // observes all previous writes to the instruction memory.
  31.         // Therefore, fence.i must be executed after we have loaded
  32.         // the code of the next app into the instruction memory.
  33.         // See also: riscv non-priv spec chapter 3, 'Zifencei' extension.
  34.         asm!("fence.i");
  35.     }
  36.     pub fn get_current_app(&self) -> usize {
  37.         self.current_app
  38.     }
  39.     pub fn move_to_next_app(&mut self) {
  40.         self.current_app += 1;
  41.     }
  42.     pub fn get_current_app_range(&self) -> (usize, usize) {
  43.         (APP_BASE_ADDRESS,APP_BASE_ADDRESS+self.app_start[self.current_app]-self.app_start[self.current_app-1])
  44.     }
  45. }
  46. pub fn get_current_app_range() -> (usize, usize) {
  47.     APP_MANAGER.exclusive_access().get_current_app_range()
  48. }
复制代码
在batch.rs中创建一个方法,计算用户栈的范围:
  1. pub fn get_user_stack_range() -> (usize, usize) {
  2.     (USER_STACK.get_sp() - USER_STACK_SIZE, USER_STACK.get_sp())
  3. }
复制代码
改造sys_wirte,如果变量不是在上述两个范围内,那么就sys_exit():
  1. /// write buf of length `len`  to a file with `fd`
  2. pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
  3.     let app_range = get_current_app_range();
  4.     let stack_range = get_user_stack_range();
  5.     //println!("range: [{:#x}, {:#x})\n", range.0,range.1);
  6.     let buf_pointer = buf as usize;
  7.     //println!("buf_pointer: {:#x}\n", buf_pointer);
  8.     if  (buf_pointer < app_range.0 || buf_pointer >= app_range.1) && (buf_pointer < stack_range.0 || buf_pointer >= stack_range.1) {
  9.         sys_exit(fd as i32)
  10.     }
  11.     match fd {
  12.         FD_STDOUT => {
  13.             let slice = unsafe { core::slice::from_raw_parts(buf, len) };
  14.             let str = core::str::from_utf8(slice).unwrap();
  15.             print!("{}", str);
  16.             len as isize
  17.         }
  18.         _ => {
  19.             panic!("Unsupported fd in sys_write!");
  20.         }
  21.     }
  22. }
复制代码
这里其实在编写过程中有一个小的踩坑的过程的(一直踩坑一直G),
最开始只想到了参数的值必须在被加载的范围内,也就是上述app_range的范围内,然后发现,APP2老报错,我把sys_exit换成println!,发现每次 访问变量 都会出问题,这时候也问了同义千问sp的作用,其中有一句让我豁然开朗:
栈指针寄存器用于跟踪当前栈顶的位置。每当函数调用发生时,参数和局部变量会被压入栈中,栈指针会相应地下移(在RISC-V中,栈向下增长)。
会想起牢丘的51单片机实验,他老说保存现场保存现场,其实保存的就是局部变量,局部变量就是存在通用寄存器里嘛!(也不知道我有没有融会贯通对)
这时候参考了别人的代码,就又加上了这部分.
测试的时候需要把自己的修改放到官方的代码:~/App/rCore-Tutorial-v3里去,git checkout ch2-lab,运行make run TEST=1.
这里运行的时候报错了:
  1. (rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add riscv64gc-unknown-none-elf
  2. riscv64gc-unknown-none-elf (installed)
  3. cargo install cargo-binutils --vers =0.3.3
  4.     Updating `ustc` index
  5.   Installing cargo-binutils v0.3.3
  6. error: failed to compile `cargo-binutils v0.3.3`, intermediate artifacts can be found at `/tmp/cargo-installvTJxCF`
  7. Caused by:
  8.   package `regex-automata v0.4.7` cannot be built because it requires rustc 1.65 or newer, while the currently active rustc version is 1.64.0-nightly
  9. make: *** [Makefile:45: env] Error 101
复制代码
说是rustc的等级太低,重新更新一下rustc,然后安装依赖:
  1. rustup update
复制代码
发现仍然无效.
但是这里踩了一个新坑,更新rust之后要看最新版本需要 重启命令行.
仍然无效的原因是:
  1. rustup show
  2. Default host: x86_64-unknown-linux-gnu
  3. rustup home:  /home/winddevil/.rustup
  4. installed toolchains
  5. --------------------
  6. stable-x86_64-unknown-linux-gnu
  7. nightly-2022-07-20-x86_64-unknown-linux-gnu
  8. nightly-2024-05-01-x86_64-unknown-linux-gnu (default)
  9. nightly-x86_64-unknown-linux-gnu
  10. installed targets for active toolchain
  11. --------------------------------------
  12. riscv64gc-unknown-none-elf
  13. x86_64-unknown-linux-gnu
  14. active toolchain
  15. ----------------
  16. nightly-2022-07-20-x86_64-unknown-linux-gnu (overridden by '/home/winddevil/App/rCore-Tutorial-v3/rust-toolchain.toml')
  17. rustc 1.64.0-nightly (9a7b7d5e5 2022-07-19)
复制代码
可以看到rust-toolchain.toml规定的channel影响了我们的版本问题.
进去把它修改为最新的channel = "nightly-2024-07-30"就可以了.
但是这样又产生新的冲突:
  1. warning: the feature `panic_info_message` has been stable since 1.82.0-nightly and no longer requires an attribute to enable
  2. --> src/lib.rs:3:12
  3.   |
  4. 3 | #![feature(panic_info_message)]
  5.   |            ^^^^^^^^^^^^^^^^^^
  6.   |
  7.   = note: `#[warn(stable_features)]` on by default
  8. error[E0599]: no method named `unwrap` found for struct `PanicMessage` in the current scope
  9. --> src/lang_items.rs:5:36
  10.   |
  11. 5 |     let err = panic_info.message().unwrap();
  12.   |                                    ^^^^^^ method not found in `PanicMessage<'_>`
  13. For more information about this error, try `rustc --explain E0599`.
  14. warning: `user_lib` (lib) generated 1 warning
  15. error: could not compile `user_lib` (lib) due to 1 previous error; 1 warning emitted
  16. make[1]: *** [Makefile:20: binary] Error 101
  17. make[1]: Leaving directory '/home/winddevil/App/rCore-Tutorial-v3/user'
  18. make: *** [Makefile:53: kernel] Error 2
复制代码
这时候再次运行,发现第三次尝试报错,原因是它试图在距离栈底位置距离为5的地方写入10的数据,这下不管是帧头还算帧尾都在范围内了,那么为什么会是一个测试例子呢( 默认是错误的例子 ),因为距离栈底为5的位置不在栈里,而后边5个单位在栈里,这样就导致了对于全局数据位置和临时数据位置的跨越写入,这时候把代码改成:
  1. [rustsbi] RustSBI version 0.2.0-alpha.6
  2. .______       __    __      _______.___________.  _______..______   __
  3. |   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
  4. |  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
  5. |      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
  6. |  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
  7. | _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
  8. [rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
  9. [rustsbi-dtb] Hart count: cluster0 with 1 cores
  10. [rustsbi] misa: RV64ACDFIMSU
  11. [rustsbi] mideleg: ssoft, stimer, sext (0x222)
  12. [rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
  13. [rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
  14. [rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
  15. [rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
  16. [rustsbi] enter supervisor 0x80200000
  17. [kernel] Hello, world!
  18. [kernel] num_app = 2
  19. [kernel] app_0 [0x80209020, 0x8020c768)
  20. [kernel] app_1 [0x8020c768, 0x8020ffa0)
  21. [kernel] Loading app_0
  22. [kernel] Panicked at src/trap/mod.rs:45 no message
复制代码
这时候的运行结果是:
  1. [rustsbi] RustSBI version 0.2.0-alpha.6
  2. .______       __    __      _______.___________.  _______..______   __
  3. |   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
  4. |  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
  5. |      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
  6. |  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
  7. | _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
  8. [rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
  9. [rustsbi-dtb] Hart count: cluster0 with 1 cores
  10. [rustsbi] misa: RV64ACDFIMSU
  11. [rustsbi] mideleg: ssoft, stimer, sext (0x222)
  12. [rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
  13. [rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
  14. [rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
  15. [rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
  16. [rustsbi] enter supervisor 0x80200000
  17. [kernel] Hello, world!
  18. [kernel] num_app = 2
  19. [kernel] app_0 [0x80209020, 0x8020c768)
  20. [kernel] app_1 [0x8020c768, 0x8020ffa0)
  21. [kernel] Loading app_0
  22. [kernel] Application exited with code 1
  23. [kernel] Loading app_1
  24. [kernel] Panicked at src/syscall/fs.rs:27 Unsupported fd in sys_write!
复制代码
第一个测试用例通过了,但是第二个还没有.果然如此,又要继续踩坑.
可以看到是加载之后就出现了BUG,而且报了调用栈,说明是触发了panic!.
查看代码,原来是进入了sys_write的default的分支,我们试着不报panic,把这句注释掉:
  1. #![no_std]
  2. #![no_main]
  3. use core::arch::asm;
  4. #[macro_use]
  5. extern crate user_lib;
  6. extern crate core;
  7. use core::slice;
  8. use user_lib::{write, STDOUT};
  9. /// 正确输出:
  10. /// Test write0 OK!
  11. const STACK_SIZE: usize = 0x1000;
  12. unsafe fn r_sp() -> usize {
  13.     let mut sp: usize;
  14.     asm!("mv {}, sp", out(reg) sp);
  15.     sp
  16. }
  17. unsafe fn stack_range() -> (usize, usize) {
  18.     let sp = r_sp();
  19.     let top = (sp + STACK_SIZE - 1) & (!(STACK_SIZE - 1));
  20.     (top - STACK_SIZE, top)
  21. }
  22. #[no_mangle]
  23. pub fn main() -> i32 {
  24.     assert_eq!(
  25.         write(STDOUT, unsafe {
  26.             #[allow(clippy::zero_ptr)]
  27.             slice::from_raw_parts(0x0 as *const _, 10)
  28.         }),
  29.         -1
  30.     );
  31.     let (bottom, top) = unsafe { stack_range() };
  32.     assert_eq!(
  33.         write(STDOUT, unsafe {
  34.             slice::from_raw_parts((top - 5) as *const _, 10)
  35.         }),
  36.         -1
  37.     );
  38.     assert_eq!(
  39.         write(STDOUT, unsafe {
  40.             slice::from_raw_parts((bottom - 5) as *const _, 10)
  41.         }),
  42.         -1
  43.     );
  44.     // TODO: test string located in .data section
  45.     println!("Test write0 OK!");
  46.     0
  47. }
复制代码
OK,就此解决了:
  1. pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
  2.     let app_range = get_current_app_range();
  3.     let stack_range = get_user_stack_range();
  4.     //println!("range: [{:#x}, {:#x})\n", range.0,range.1);
  5.     let buf_pointer = buf as usize;
  6.     //println!("buf_pointer: {:#x}\n", buf_pointer);
  7.     if  (buf_pointer < app_range.0 || buf_pointer >= app_range.1) && (buf_pointer < stack_range.0 || buf_pointer >= stack_range.1) {
  8.         println!("Out of range!");
  9.         return (len as isize)
  10.         //sys_exit(fd as i32)
  11.     }
  12.     match fd {
  13.         FD_STDOUT => {
  14.             let slice = unsafe { core::slice::from_raw_parts(buf, len) };
  15.             let str = core::str::from_utf8(slice).unwrap();
  16.             print!("{}", str);
  17.             len as isize
  18.         }
  19.         _ => {
  20.             panic!("Unsupported fd in sys_write!");
  21.         }
  22.   
复制代码
这里不要因为解决了就忘记解读第二个测例test1_write1.rs,查看它的源码,发现它分别测试的是:

  • 能否跳过未支持的fd
  • 能否访问

    • 存在data段的静态变量
    • 存在stack段的局部变量
    • 存在stack段的局部变量的部分指针

  1. [rustsbi] RustSBI version 0.2.0-alpha.6
  2. .______       __    __      _______.___________.  _______..______   __
  3. |   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
  4. |  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
  5. |      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
  6. |  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
  7. | _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
  8. [rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
  9. [rustsbi-dtb] Hart count: cluster0 with 1 cores
  10. [rustsbi] misa: RV64ACDFIMSU
  11. [rustsbi] mideleg: ssoft, stimer, sext (0x222)
  12. [rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
  13. [rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
  14. [rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
  15. [rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
  16. [rustsbi] enter supervisor 0x80200000
  17. [kernel] Hello, world!
  18. [kernel] num_app = 2
  19. [kernel] app_0 [0x80209020, 0x8020c768)
  20. [kernel] app_1 [0x8020c768, 0x8020ffa0)
  21. [kernel] Loading app_0
  22. Out of range!
  23. Out of range!
  24. Out of range!
  25. [kernel] Application exited with code -1
  26. [kernel] Loading app_1
  27. [kernel] Panicked at src/syscall/fs.rs:29 Unsupported fd in sys_write!
复制代码
问答作业

1.  正确进入 U 态后,程序的特征还应有:使用 S 态特权指令,访问 S 态寄存器后会报错。请自行测试这些内容 (运行 Rust 三个 bad 测例 ) ,描述程序出错行为,注明你使用的 sbi 及其版本。

这里可以直接看我们在上一部分的运行结果:
  1. TEST ?= 0
  2. ifeq ($(TEST), 0)
  3.         APPS :=  $(filter-out $(wildcard $(APP_DIR)/test*.rs), $(wildcard $(APP_DIR)/*.rs))
  4. else
  5.         APPS :=  $(wildcard $(APP_DIR)/test$(TEST)*.rs)
  6. endif
  7. ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
复制代码
出错行为由trap_handler分类和处理:
  1. /// write buf of length `len`  to a file with `fd`
  2. pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
  3.     let app_range = get_current_app_range();
  4.     let stack_range = get_user_stack_range();
  5.     // println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
  6.     // println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
  7.     let buf_begin_pointer = buf as usize;
  8.     let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
  9.     // println!("buf_pointer: {:#x}", buf_pointer);
  10.     if  ((buf_begin_pointer < app_range.0 || buf_begin_pointer >= app_range.1) &&
  11.         (buf_begin_pointer < stack_range.0 || buf_begin_pointer >= stack_range.1))||
  12.         ((buf_end_pointer < app_range.0 || buf_end_pointer >= app_range.1) &&
  13.         (buf_end_pointer  < stack_range.0 || buf_end_pointer  >= stack_range.1))
  14.     {
  15.         println!("out of range!");
  16.         return -1 as isize;
  17.         // sys_exit(fd as i32)
  18.     }
  19.     match fd {
  20.         FD_STDOUT => {
  21.             let slice = unsafe { core::slice::from_raw_parts(buf, len) };
  22.             let str = core::str::from_utf8(slice).unwrap();
  23.             print!("{}", str);
  24.             len as isize
  25.         }
  26.         _ => {
  27.             panic!("Unsupported fd in sys_write!");
  28.         }
  29.     }
  30. }
复制代码
把其中报错的部分挑出来.
app_1:
  1. /// write buf of length `len`  to a file with `fd`
  2. pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
  3.     let app_range = get_current_app_range();
  4.     let stack_range = get_user_stack_range();
  5.     // println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
  6.     // println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
  7.     let buf_begin_pointer = buf as usize;
  8.     let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
  9.     // println!("buf_begin_pointer: {:#x}", buf_begin_pointer);
  10.     // println!("buf_end_pointer: {:#x}", buf_end_pointer);
  11.     if !(
  12.             (buf_begin_pointer >= app_range.0 && buf_begin_pointer < app_range.1) &&
  13.             (buf_end_pointer >= app_range.0 && buf_end_pointer < app_range.1)
  14.         )&&
  15.         !(
  16.             (buf_begin_pointer >= stack_range.0 && buf_begin_pointer < stack_range.1) &&
  17.             (buf_end_pointer >= stack_range.0 && buf_end_pointer < stack_range.1)
  18.         )
  19.     {
  20.         println!("out of range!");
  21.         return -1 as isize;
  22.         // sys_exit(fd as i32)
  23.     }
  24.     match fd {
  25.         FD_STDOUT => {
  26.             let slice = unsafe { core::slice::from_raw_parts(buf, len) };
  27.             let str = core::str::from_utf8(slice).unwrap();
  28.             print!("{}", str);
  29.             len as isize
  30.         }
  31.         _ => {
  32.             panic!("Unsupported fd in sys_write!");
  33.         }
  34.     }
  35. }
复制代码
其源码:
  1. [rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
  2. .______       __    __      _______.___________.  _______..______   __
  3. |   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
  4. |  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
  5. |      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
  6. |  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
  7. | _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
  8. [rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
  9. [rustsbi] Platform Name      : riscv-virtio,qemu
  10. [rustsbi] Platform SMP       : 1
  11. [rustsbi] Platform Memory    : 0x80000000..0x88000000
  12. [rustsbi] Boot HART          : 0
  13. [rustsbi] Device Tree Region : 0x87000000..0x87000f02
  14. [rustsbi] Firmware Address   : 0x80000000
  15. [rustsbi] Supervisor Address : 0x80200000
  16. [rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
  17. [rustsbi] pmp02: 0x80000000..0x80200000 (---)
  18. [rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
  19. [rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
  20. [kernel] Hello, world!
  21. [kernel] num_app = 7
  22. [kernel] app_0 [0x8020b048, 0x8020c370)
  23. [kernel] app_1 [0x8020c370, 0x8020d740)
  24. [kernel] app_2 [0x8020d740, 0x8020ece8)
  25. [kernel] app_3 [0x8020ece8, 0x802100a0)
  26. [kernel] app_4 [0x802100a0, 0x80211450)
  27. [kernel] app_5 [0x80211450, 0x80212d80)
  28. [kernel] app_6 [0x80212d80, 0x802147c0)
  29. [kernel] Loading app_0
  30. Hello, world!
  31. [kernel] Application exited with code 0
  32. [kernel] Loading app_1
  33. Into Test store_fault, we will insert an invalid store operation...
  34. Kernel should kill this application!
  35. [kernel] PageFault in application, kernel killed it.
  36. [kernel] Loading app_2
  37. 3^10000=5079(MOD 10007)
  38. 3^20000=8202(MOD 10007)
  39. 3^30000=8824(MOD 10007)
  40. 3^40000=5750(MOD 10007)
  41. 3^50000=3824(MOD 10007)
  42. 3^60000=8516(MOD 10007)
  43. 3^70000=2510(MOD 10007)
  44. 3^80000=9379(MOD 10007)
  45. 3^90000=2621(MOD 10007)
  46. 3^100000=2749(MOD 10007)
  47. Test power OK!
  48. [kernel] Application exited with code 0
  49. [kernel] Loading app_3
  50. Try to execute privileged instruction in U Mode
  51. Kernel should kill this application!
  52. [kernel] IllegalInstruction in application, kernel killed it.
  53. [kernel] Loading app_4
  54. Try to access privileged CSR in U Mode
  55. Kernel should kill this application!
  56. [kernel] IllegalInstruction in application, kernel killed it.
  57. [kernel] Loading app_5
  58. out of range!
  59. out of range!
  60. out of range!
  61. Test write0 OK!
  62. [kernel] Application exited with code 0
  63. [kernel] Loading app_6
  64. == Begin stack trace ==
  65. 0x000000008020149a, fp = 0x0000000080206d40
  66. 0x0000000080201c02, fp = 0x0000000080206d80
  67. 0x000000008020073c, fp = 0x0000000080206e00
  68. 0x0000000080201790, fp = 0x0000000080206ef0
  69. 0x0000000080200d8c, fp = 0x0000000080209fc0
  70. 0x0000000080400032, fp = 0x000000008020a000
  71. 0x0000000000000000, fp = 0x0000000000000000
  72. == End stack trace ==
  73. make: *** [Makefile:64: run-inner] Error 255
复制代码
可以看到它是尝试写入了一个空指针.这时候就会触发trap_handler里的Trap::Exception(Exception::StoreFault),意为访问了无效的内存地址.
app_3:
  1. /// write buf of length `len`  to a file with `fd`
  2. pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
  3.     let app_range = get_current_app_range();
  4.     let stack_range = get_user_stack_range();
  5.     // println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
  6.     // println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
  7.     let buf_begin_pointer = buf as usize;
  8.     let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
  9.     // println!("buf_pointer: {:#x}", buf_pointer);
  10.     if !(
  11.             (buf_begin_pointer >= app_range.0 && buf_begin_pointer < app_range.1) &&
  12.             (buf_end_pointer >= app_range.0 && buf_end_pointer < app_range.1)
  13.         )||
  14.         (
  15.             (buf_begin_pointer >= stack_range.0 && buf_begin_pointer < stack_range.1) &&
  16.             (buf_end_pointer >= stack_range.0 && buf_end_pointer < stack_range.1)
  17.         )
  18.     {
  19.         println!("out of range!");
  20.         return -1 as isize;
  21.         // sys_exit(fd as i32)
  22.     }
  23.     match fd {
  24.         FD_STDOUT => {
  25.             let slice = unsafe { core::slice::from_raw_parts(buf, len) };
  26.             let str = core::str::from_utf8(slice).unwrap();
  27.             print!("{}", str);
  28.             len as isize
  29.         }
  30.         _ => {
  31.             -1 as isize
  32.             //panic!("Unsupported fd in sys_write!");
  33.         }
  34.     }
  35. }
复制代码
其源码:
  1. [rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
  2. .______       __    __      _______.___________.  _______..______   __
  3. |   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
  4. |  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
  5. |      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
  6. |  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
  7. | _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
  8. [rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
  9. [rustsbi] Platform Name      : riscv-virtio,qemu
  10. [rustsbi] Platform SMP       : 1
  11. [rustsbi] Platform Memory    : 0x80000000..0x88000000
  12. [rustsbi] Boot HART          : 0
  13. [rustsbi] Device Tree Region : 0x87000000..0x87000f02
  14. [rustsbi] Firmware Address   : 0x80000000
  15. [rustsbi] Supervisor Address : 0x80200000
  16. [rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
  17. [rustsbi] pmp02: 0x80000000..0x80200000 (---)
  18. [rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
  19. [rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
  20. [kernel] Hello, world!
  21. [kernel] num_app = 7
  22. [kernel] app_0 [0x8020b048, 0x8020c370)
  23. [kernel] app_1 [0x8020c370, 0x8020d740)
  24. [kernel] app_2 [0x8020d740, 0x8020ece8)
  25. [kernel] app_3 [0x8020ece8, 0x802100a0)
  26. [kernel] app_4 [0x802100a0, 0x80211450)
  27. [kernel] app_5 [0x80211450, 0x80212d80)
  28. [kernel] app_6 [0x80212d80, 0x802147c0)
  29. [kernel] Loading app_0
  30. Hello, world!
  31. [kernel] Application exited with code 0
  32. [kernel] Loading app_1
  33. Into Test store_fault, we will insert an invalid store operation...
  34. Kernel should kill this application!
  35. [kernel] PageFault in application, kernel killed it.
  36. [kernel] Loading app_2
  37. 3^10000=5079(MOD 10007)
  38. 3^20000=8202(MOD 10007)
  39. 3^30000=8824(MOD 10007)
  40. 3^40000=5750(MOD 10007)
  41. 3^50000=3824(MOD 10007)
  42. 3^60000=8516(MOD 10007)
  43. 3^70000=2510(MOD 10007)
  44. 3^80000=9379(MOD 10007)
  45. 3^90000=2621(MOD 10007)
  46. 3^100000=2749(MOD 10007)
  47. Test power OK!
  48. [kernel] Application exited with code 0
  49. [kernel] Loading app_3
  50. Try to execute privileged instruction in U Mode
  51. Kernel should kill this application!
  52. [kernel] IllegalInstruction in application, kernel killed it.
  53. [kernel] Loading app_4
  54. Try to access privileged CSR in U Mode
  55. Kernel should kill this application!
  56. [kernel] IllegalInstruction in application, kernel killed it.
  57. [kernel] Loading app_5
  58. out of range!
  59. out of range!
  60. out of range!
  61. Test write0 OK!
  62. [kernel] Application exited with code 0
  63. [kernel] Loading app_6
  64. string from data section
  65. strinstring from stack section
  66. strin
  67. Test write1 OK!
  68. [kernel] Application exited with code 0
  69. All applications completed!
复制代码
可见它是尝试调用sret,这是需要S特权级的.因此触发trap_handler的Trap::Exception(Exception::IllegalInstruction),意为使用了非法指令.
app_4:
  1. #![no_std]
  2. #![no_main]
  3. #[macro_use]
  4. extern crate user_lib;
  5. use user_lib::write;
  6. const DATA_STRING: &str = "string from data section\n";
  7. const STDOUT: usize = 1;
  8. /// 正确输出:
  9. /// string from data section
  10. /// strinstring from stack section
  11. /// strin
  12. /// Test write1 OK!
  13. #[no_mangle]
  14. pub fn main() -> i32 {
  15.     assert_eq!(write(1234, DATA_STRING.as_bytes()), -1);
  16.     assert_eq!(
  17.         write(STDOUT, DATA_STRING.as_bytes()),
  18.         DATA_STRING.len() as isize
  19.     );
  20.     assert_eq!(write(STDOUT, &DATA_STRING.as_bytes()[..5]), 5);
  21.     let stack_string = "string from stack section\n";
  22.     assert_eq!(
  23.         write(STDOUT, stack_string.as_bytes()),
  24.         stack_string.len() as isize
  25.     );
  26.     assert_eq!(write(STDOUT, &stack_string.as_bytes()[..5]), 5);
  27.     println!("\nTest write1 OK!");
  28.     0
  29. }
复制代码
其源码:
  1. [rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
  2. .______       __    __      _______.___________.  _______..______   __
  3. |   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
  4. |  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
  5. |      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
  6. |  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
  7. | _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
  8. [rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
  9. [rustsbi] Platform Name      : riscv-virtio,qemu
  10. [rustsbi] Platform SMP       : 1
  11. [rustsbi] Platform Memory    : 0x80000000..0x88000000
  12. [rustsbi] Boot HART          : 0
  13. [rustsbi] Device Tree Region : 0x87000000..0x87000f02
  14. [rustsbi] Firmware Address   : 0x80000000
  15. [rustsbi] Supervisor Address : 0x80200000
  16. [rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
  17. [rustsbi] pmp02: 0x80000000..0x80200000 (---)
  18. [rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
  19. [rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
  20. [kernel] Hello, world!
  21. [kernel] num_app = 5
  22. [kernel] app_0 [0x8020a038, 0x8020b360)
  23. [kernel] app_1 [0x8020b360, 0x8020c730)
  24. [kernel] app_2 [0x8020c730, 0x8020dcd8)
  25. [kernel] app_3 [0x8020dcd8, 0x8020f090)
  26. [kernel] app_4 [0x8020f090, 0x80210440)
  27. [kernel] Loading app_0
  28. Hello, world!
  29. [kernel] Application exited with code 0
  30. [kernel] Loading app_1
  31. Into Test store_fault, we will insert an invalid store operation...
  32. Kernel should kill this application!
  33. [kernel] PageFault in application, kernel killed it.
  34. [kernel] Loading app_2
  35. 3^10000=5079(MOD 10007)
  36. 3^20000=8202(MOD 10007)
  37. 3^30000=8824(MOD 10007)
  38. 3^40000=5750(MOD 10007)
  39. 3^50000=3824(MOD 10007)
  40. 3^60000=8516(MOD 10007)
  41. 3^70000=2510(MOD 10007)
  42. 3^80000=9379(MOD 10007)
  43. 3^90000=2621(MOD 10007)
  44. 3^100000=2749(MOD 10007)
  45. Test power OK!
  46. [kernel] Application exited with code 0
  47. [kernel] Loading app_3
  48. Try to execute privileged instruction in U Mode
  49. Kernel should kill this application!
  50. [kernel] IllegalInstruction in application, kernel killed it.
  51. [kernel] Loading app_4
  52. Try to access privileged CSR in U Mode
  53. Kernel should kill this application!
  54. [kernel] IllegalInstruction in application, kernel killed it.
  55. All applications completed!
复制代码
可见它尝试写入sstatus,也即尝试访问S的CSR,因此触发trap_handler的Trap::Exception(Exception::IllegalInstruction),意为使用了非法指令,但是这里不同的是用指令访问了S特权级才能访问的寄存器.
SBI的版本在os/Cargo.toml可以看到:
  1. #[no_mangle]
  2. /// handle an interrupt, exception, or system call from user space
  3. pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
  4.     let scause = scause::read(); // get trap cause
  5.     let stval = stval::read(); // get extra value
  6.     match scause.cause() {
  7.         Trap::Exception(Exception::UserEnvCall) => {
  8.             cx.sepc += 4;
  9.             cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize;
  10.         }
  11.         Trap::Exception(Exception::StoreFault) | Trap::Exception(Exception::StorePageFault) => {
  12.             println!("[kernel] PageFault in application, kernel killed it.");
  13.             run_next_app();
  14.         }
  15.         Trap::Exception(Exception::IllegalInstruction) => {
  16.             println!("[kernel] IllegalInstruction in application, kernel killed it.");
  17.             run_next_app();
  18.         }
  19.         _ => {
  20.             panic!(
  21.                 "Unsupported trap {:?}, stval = {:#x}!",
  22.                 scause.cause(),
  23.                 stval
  24.             );
  25.         }
  26.     }
  27.     cx
  28. }
复制代码
2. 请结合用例理解 trap.S 中两个函数 __alltraps 和 __restore 的作用,并回答如下几个问题:

这里是trap.S的内容:
  1. [kernel] Loading app_1
  2. Into Test store_fault, we will insert an invalid store operation...
  3. Kernel should kill this application!
  4. [kernel] PageFault in application, kernel killed it.
复制代码
1.  L40:刚进入 __restore 时,a0 代表了什么值。请指出 __restore 的两种使用情景。

刚刚进入__restore的时候是a0是我们传入__restore的参数,我们在run_next_app中调用了这个函数:
  1. #![no_std]
  2. #![no_main]
  3. #[macro_use]
  4. extern crate user_lib;
  5. #[no_mangle]
  6. fn main() -> i32 {
  7.     println!("Into Test store_fault, we will insert an invalid store operation...");
  8.     println!("Kernel should kill this application!");
  9.     unsafe {
  10.         core::ptr::null_mut::<u8>().write_volatile(0);
  11.     }
  12.     0
  13. }
复制代码
可以看到传入的是我们主动制造的TrapContext的指针,TrapContext内容是APP加载位置APP_BASE_ADDRESS和用户栈的指针.
它被调用的情景分为两种:

  • 一个APP运行结束或者出错之后的APP切换
  • 在内核工作之后开始APP的加载和运行
2. L46-L51:这几行汇编代码特殊处理了哪些寄存器?这些寄存器的的值对于进入用户态有何意义?请分别解释。

代码在此:
  1. [kernel] Loading app_3
  2. Try to execute privileged instruction in U Mode
  3. Kernel should kill this application!
  4. [kernel] IllegalInstruction in application, kernel killed it.
复制代码
这一部分是先把存在栈中的内容转移到t0~t2,然后再把它们还原回sstatus,sepc和sscratch.
这里不能直接移动的原因是ld可以操作寄存器的指针偏移,而csrw只能操作寄存器和寄存器.
15.png

16.png

sstatus:SPP 等字段给出 Trap 发生之前 CPU 处在哪个特权级(S/U)等信息.
sepc:当 Trap 是一个异常的时候,记录 Trap 发生之前执行的最后一条指令的地址
sscratch:是保存Trap时的sscratch的值,实际上是当时的用户栈的指针.
后续调用csrrw sp, sscratch, sp使得sp被交换回用户栈的指针.
3. L53-L59:为何跳过了 x2 和 x4?

官方文档:

  • 但这里也有一些例外,如 x0 被硬编码为 0 ,它自然不会有变化;还有 tp(x4) 寄存器,除非我们手动出于一些特殊用途使用它,否则一般也不会被用到。
  • 我们在这里也不保存 sp(x2),因为我们要基于它来找到每个寄存器应该被保存到的正确的位置。
4. L63:该指令之后,sp 和 sscratch 中的值分别有什么意义?

此指令后,sp->user stack sscratch->kernel stack,因此sp重新指向用户栈.
5. __restore:中发生状态切换在哪一条指令?为何该指令执行之后会进入用户态?

发生状态切换是在sret.
硬件上是这个原因,调用它之后寄存器会发生变化.
17.png

6. L13:该指令之后,sp 和 sscratch 中的值分别有什么意义?

这个指令之后就把sp和sscratch切换了,就会导致sp->kernel stack, sscratch->user stack.
7. 从 U 态进入 S 态是哪一条指令发生的?

应该是再用户态的ecall指令发生的.
3. 对于任何中断,__alltraps 中都需要保存所有寄存器吗?你有没有想到一些加速 __alltraps 的方法?简单描述你的想法。

只需要保存中断过程中可能需要改变的寄存器.
这里可提前读取scause的内容,根据情况来处理,以本章trap_handler为例:
  1. /// write buf of length `len`  to a file with `fd`
  2. pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
  3.     let app_range = get_current_app_range();
  4.     let stack_range = get_user_stack_range();
  5.     // println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
  6.     // println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
  7.     let buf_begin_pointer = buf as usize;
  8.     let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
  9.     // println!("buf_pointer: {:#x}", buf_pointer);
  10.     if  ((buf_begin_pointer < app_range.0 || buf_begin_pointer >= app_range.1) &&
  11.         (buf_begin_pointer < stack_range.0 || buf_begin_pointer >= stack_range.1))||
  12.         ((buf_end_pointer < app_range.0 || buf_end_pointer >= app_range.1) &&
  13.         (buf_end_pointer  < stack_range.0 || buf_end_pointer  >= stack_range.1))
  14.     {
  15.         println!("out of range!");
  16.         return -1 as isize;
  17.         // sys_exit(fd as i32)
  18.     }
  19.     match fd {
  20.         FD_STDOUT => {
  21.             let slice = unsafe { core::slice::from_raw_parts(buf, len) };
  22.             let str = core::str::from_utf8(slice).unwrap();
  23.             print!("{}", str);
  24.             len as isize
  25.         }
  26.         _ => {
  27.             panic!("Unsupported fd in sys_write!");
  28.         }
  29.     }
  30. }
复制代码
当检测到scause是Environment call from U-mode的时候进行相应的调用,因此需要保存上下文,而遇到Store access fault和Illegal instruction的时候则不需要保存.
这里是mcause的位描述,scause是它的一个子集,是一样的布局:
18.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册