写在前面

本随笔是非常菜的菜鸡写的。如有问题请及时提出。

可以联系:1160712160@qq.com

GitHhub:
https://github.com/WindDevil
(目前啥也没有

编程题

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

首先在这里卡了我很久的是调用栈保存在哪里,回想到上一部分画的图,其实就在
.bss
段后边.

这里注意
sp
寄存器是栈指针寄存器,
fp
寄存器是帧指针寄存器,是不一样的.

这里重提一下
os/.cargo/config
的内容,
"-Cforce-frame-pointers=yes"
代表保存栈指针:

# os/.cargo/config
[build]
target = "riscv64gc-unknown-none-elf"

[target.riscv64gc-unknown-none-elf]
 rustflags = [
     "-Clink-arg=-Tsrc/linker-qemu.ld", "-Cforce-frame-pointers=yes"
 ]

此时可以通过
fp
的值(保证它不在trap过程中在用户栈或者内核栈中).

这时候可以创建
os/src/stack_trace.rs
:

use core::{arch::asm, ptr};

pub unsafe fn print_stack_trace() -> () {
    let mut fp: *const usize;
    asm!("mv {}, fp", out(reg) fp);

    println!("== Begin stack trace ==");
    while fp != ptr::null() {
        let saved_ra = *fp.sub(1);
        let saved_fp = *fp.sub(2);

        println!("0x{:016x}, fp = 0x{:016x}", saved_ra, saved_fp);

        fp = saved_fp as *const usize;
    }
    println!("== End stack trace ==");
}


main.rs
里把它加入
main.rs
作为一个子模块:

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!");
,代替原来的正常关机.

impl AppManager {
    pub fn print_app_info(&self) {
        println!("[kernel] num_app = {}", self.num_app);
        for i in 0..self.num_app {
            println!(
                "[kernel] app_{} [{:#x}, {:#x})",
                i,
                self.app_start[i],
                self.app_start[i + 1]
            );
        }
    }

    unsafe fn load_app(&self, app_id: usize) {
        if app_id >= self.num_app {
            println!("All applications completed!");
            panic!("Shutdown machine!");
            //shutdown(false);
        }
        println!("[kernel] Loading app_{}", app_id);
        // clear app area
        core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
        let app_src = core::slice::from_raw_parts(
            self.app_start[app_id] as *const u8,
            self.app_start[app_id + 1] - self.app_start[app_id],
        );
        let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
        app_dst.copy_from_slice(app_src);
        // Memory fence about fetching the instruction memory
        // It is guaranteed that a subsequent instruction fetch must
        // observes all previous writes to the instruction memory.
        // Therefore, fence.i must be executed after we have loaded
        // the code of the next app into the instruction memory.
        // See also: riscv non-priv spec chapter 3, 'Zifencei' extension.
        asm!("fence.i");
    }

    pub fn get_current_app(&self) -> usize {
        self.current_app
    }

    pub fn move_to_next_app(&mut self) {
        self.current_app += 1;
    }
}

然后直接在
os
目录下
make run
.如果报错了

error: unused import: `crate::sbi::shutdown`
  --> src/batch.rs:3:5
   |
3  | use crate::sbi::shutdown;
   |     ^^^^^^^^^^^^^^^^^^^^
   |

直接注释掉
use crate::sbi::shutdown;
即可.

这样就可以得到结果:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] num_app = 5
[kernel] app_0 [0x8020a038, 0x8020b360)
[kernel] app_1 [0x8020b360, 0x8020c730)
[kernel] app_2 [0x8020c730, 0x8020dcd8)
[kernel] app_3 [0x8020dcd8, 0x8020f090)
[kernel] app_4 [0x8020f090, 0x80210440)
[kernel] Loading app_0
Hello, world!
[kernel] Application exited with code 0
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_2
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
All applications completed!
== Begin stack trace ==
0x0000000080201244, fp = 0x0000000080206cf0
0x0000000080201a78, fp = 0x0000000080206d30
0x0000000080200ed8, fp = 0x0000000080206da0
0x00000000802010bc, fp = 0x0000000080206e00
0x0000000080201724, fp = 0x0000000080206ef0
0x0000000080200a70, fp = 0x0000000080208fc0
0x0000000080400032, fp = 0x0000000080209000
0x0000000000000000, fp = 0x0000000000000000
== End stack trace ==
make: *** [Makefile:64: run-inner] Error 255

后续题目

问答题

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

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

这里注意去看之前提到的
trap
处理流程:

  1. print!
    ->
    print
    ->
    Stdout.write_fmt
    ->
    write
    ->
    sys_write
    ->
    syscall
    是在
    user
    层的函数调用链.
  2. syscall
    ->
    sys_write
    ->
    print!
    ->
    Stdout.write_fmt
    ->
    console_putchar
    是在
    os
    层的函数调用链.
  3. 使用
    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 模式。

使用
make debug
进入
debug
模式,使用GDB命令
break rust_main
打断点,然后使用
c
命令使得程序停在
rust_main
之前.

使用指令
info register mideleg

info register medeleg
可以读取到两个寄存器的值:

mideleg        0x666    1638
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

通过查阅手册我们可以得出特权指令分为如下图所示:

sret的作用:

mret的作用:

wfi的作用:

sfence.vma的作用:

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

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

图中的CSR相关指令为:

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 支持哪些中断 / 异常?如何判断进入内核是由于中断还是异常?描述陷入内核时的几个重要寄存器及其值。

这里就还是要看这张图了

要判断当前的中断和异常情况只需要看
scause
,
RISC-V手册 (ustc.edu.cn)
对它的描述是:
scause 按图 10.3 根据异常类型设置,stval 被设置成出错的地址或者其它特定异常的信息字。

Trap
时重要的寄存器也还是看这张图,这里直接列出M模式下CSR的列表,再S模式下的CSR命名则是把名字里的
m
,更换为
s
:

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的作用:

mret的作用:

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

这个直接联系到我们对于
TrapContext
的定义代码:

pub struct TrapContext {
    /// general regs[0..31]
    pub x: [usize; 32],
    /// CSR sstatus      
    pub sstatus: Sstatus,
    /// CSR sepc
    pub sepc: usize,
}

这里我们可以反过来回想到,
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大小作为偏移量,然后通过基指针计算即可:

impl AppManager {
    pub fn print_app_info(&self) {
        println!("[kernel] num_app = {}", self.num_app);
        for i in 0..self.num_app {
            println!(
                "[kernel] app_{} [{:#x}, {:#x})",
                i,
                self.app_start[i],
                self.app_start[i + 1]
            );
        }
    }

    unsafe fn load_app(&self, app_id: usize) {
        if app_id >= self.num_app {
            println!("All applications completed!");
            //panic!("Shutdown machine!");
            shutdown(false);
        }
        println!("[kernel] Loading app_{}", app_id);
        // clear app area
        core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
        let app_src = core::slice::from_raw_parts(
            self.app_start[app_id] as *const u8,
            self.app_start[app_id + 1] - self.app_start[app_id],
        );
        let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
        app_dst.copy_from_slice(app_src);
        // Memory fence about fetching the instruction memory
        // It is guaranteed that a subsequent instruction fetch must
        // observes all previous writes to the instruction memory.
        // Therefore, fence.i must be executed after we have loaded
        // the code of the next app into the instruction memory.
        // See also: riscv non-priv spec chapter 3, 'Zifencei' extension.
        asm!("fence.i");
    }

    pub fn get_current_app(&self) -> usize {
        self.current_app
    }

    pub fn move_to_next_app(&mut self) {
        self.current_app += 1;
    }

    pub fn get_current_app_range(&self) -> (usize, usize) {
        (APP_BASE_ADDRESS,APP_BASE_ADDRESS+self.app_start[self.current_app]-self.app_start[self.current_app-1])
    }
}

pub fn get_current_app_range() -> (usize, usize) {
    APP_MANAGER.exclusive_access().get_current_app_range()
}


batch.rs
中创建一个方法,计算用户栈的范围:

pub fn get_user_stack_range() -> (usize, usize) {
    (USER_STACK.get_sp() - USER_STACK_SIZE, USER_STACK.get_sp())
}

改造
sys_wirte
,如果变量不是在上述两个范围内,那么就
sys_exit()
:

/// write buf of length `len`  to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
    let app_range = get_current_app_range();
    let stack_range = get_user_stack_range();
    //println!("range: [{:#x}, {:#x})\n", range.0,range.1);
    let buf_pointer = buf as usize;
    //println!("buf_pointer: {:#x}\n", buf_pointer);
    if  (buf_pointer < app_range.0 || buf_pointer >= app_range.1) && (buf_pointer < stack_range.0 || buf_pointer >= stack_range.1) {
        sys_exit(fd as i32)
    }
    match fd {
        FD_STDOUT => {
            let slice = unsafe { core::slice::from_raw_parts(buf, len) };
            let str = core::str::from_utf8(slice).unwrap();
            print!("{}", str);
            len as isize
        }
        _ => {
            panic!("Unsupported fd in sys_write!");
        }
    }
}

这里其实在编写过程中有一个小的踩坑的过程的(一直踩坑一直G),

最开始只想到了参数的值必须在被加载的范围内,也就是上述
app_range
的范围内,然后发现,APP2老报错,我把
sys_exit
换成
println!
,发现每次
访问变量
都会出问题,这时候也问了同义千问
sp
的作用,其中有一句让我豁然开朗:
栈指针寄存器用于跟踪当前栈顶的位置。每当函数调用发生时,参数和局部变量会被压入栈中,栈指针会相应地下移(在RISC-V中,栈向下增长)。

会想起牢丘的51单片机实验,他老说保存现场保存现场,其实保存的就是局部变量,局部变量就是存在通用寄存器里嘛!(也不知道我有没有融会贯通对)

这时候参考了别人的代码,就又加上了这部分.

测试的时候需要把自己的修改放到官方的代码
:~/App/rCore-Tutorial-v3
里去,
git checkout ch2-lab
,运行
make run TEST=1
.

这里运行的时候报错了:

(rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add riscv64gc-unknown-none-elf
riscv64gc-unknown-none-elf (installed)
cargo install cargo-binutils --vers =0.3.3
    Updating `ustc` index
  Installing cargo-binutils v0.3.3
error: failed to compile `cargo-binutils v0.3.3`, intermediate artifacts can be found at `/tmp/cargo-installvTJxCF`

Caused by:
  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
make: *** [Makefile:45: env] Error 101

说是
rustc
的等级太低,重新更新一下
rustc
,然后安装依赖:

rustup update

发现仍然无效.

但是这里踩了一个新坑,更新
rust
之后要看最新版本需要
重启命令行
.

仍然无效的原因是:

rustup show
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/winddevil/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu
nightly-2022-07-20-x86_64-unknown-linux-gnu
nightly-2024-05-01-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu

installed targets for active toolchain
--------------------------------------

riscv64gc-unknown-none-elf
x86_64-unknown-linux-gnu

active toolchain
----------------

nightly-2022-07-20-x86_64-unknown-linux-gnu (overridden by '/home/winddevil/App/rCore-Tutorial-v3/rust-toolchain.toml')
rustc 1.64.0-nightly (9a7b7d5e5 2022-07-19)

可以看到
rust-toolchain.toml
规定的
channel
影响了我们的版本问题.

进去把它修改为最新的
channel = "nightly-2024-07-30"
就可以了.

但是这样又产生新的冲突:

warning: the feature `panic_info_message` has been stable since 1.82.0-nightly and no longer requires an attribute to enable
 --> src/lib.rs:3:12
  |
3 | #![feature(panic_info_message)]
  |            ^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(stable_features)]` on by default

error[E0599]: no method named `unwrap` found for struct `PanicMessage` in the current scope
 --> src/lang_items.rs:5:36
  |
5 |     let err = panic_info.message().unwrap();
  |                                    ^^^^^^ method not found in `PanicMessage<'_>`

For more information about this error, try `rustc --explain E0599`.
warning: `user_lib` (lib) generated 1 warning
error: could not compile `user_lib` (lib) due to 1 previous error; 1 warning emitted
make[1]: *** [Makefile:20: binary] Error 101
make[1]: Leaving directory '/home/winddevil/App/rCore-Tutorial-v3/user'
make: *** [Makefile:53: kernel] Error 2

注释掉
App/rCore-Tutorial-v3/user/src/lib.rs

#![feature(panic_info_message)]
.

把所有的
panic_info.message().unwarp()
改成
panic_info.message().as_str().unwrap_or("no message")
就行了.

不修改的时候的输出:

[rustsbi] RustSBI version 0.2.0-alpha.6
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|

[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: ssoft, stimer, sext (0x222)
[rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
[rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
[rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
[rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
[rustsbi] enter supervisor 0x80200000
[kernel] Hello, world!
[kernel] num_app = 2
[kernel] app_0 [0x80209020, 0x8020c768)
[kernel] app_1 [0x8020c768, 0x8020ffa0)
[kernel] Loading app_0
[kernel] Panicked at src/trap/mod.rs:45 no message

可以看到加载app0的时候就出现了
panic
.

修改之后:

[rustsbi] RustSBI version 0.2.0-alpha.6
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|

[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: ssoft, stimer, sext (0x222)
[rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
[rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
[rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
[rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
[rustsbi] enter supervisor 0x80200000
[kernel] Hello, world!
[kernel] num_app = 2
[kernel] app_0 [0x80209020, 0x8020c768)
[kernel] app_1 [0x8020c768, 0x8020ffa0)
[kernel] Loading app_0
[kernel] Application exited with code 1
[kernel] Loading app_1
[kernel] Panicked at src/syscall/fs.rs:27 Unsupported fd in sys_write!

可以看到
app_0
的运行过程中触发了
Application exited with code 1
,没按照预期运行.而
app_1
则是更加重量级地跳了.

因为我们可以面向结果编程,所以我打算自己看一看
app_0
的测例是什么贵物;

#![no_std]
#![no_main]

use core::arch::asm;

#[macro_use]
extern crate user_lib;
extern crate core;
use core::slice;
use user_lib::{write, STDOUT};

/// 正确输出:
/// Test write0 OK!

const STACK_SIZE: usize = 0x1000;

unsafe fn r_sp() -> usize {
    let mut sp: usize;
    asm!("mv {}, sp", out(reg) sp);
    sp
}

unsafe fn stack_range() -> (usize, usize) {
    let sp = r_sp();
    let top = (sp + STACK_SIZE - 1) & (!(STACK_SIZE - 1));
    (top - STACK_SIZE, top)
}

#[no_mangle]
pub fn main() -> i32 {
    assert_eq!(
        write(STDOUT, unsafe {
            #[allow(clippy::zero_ptr)]
            slice::from_raw_parts(0x0 as *const _, 10)
        }),
        -1
    );
    let (bottom, top) = unsafe { stack_range() };
    assert_eq!(
        write(STDOUT, unsafe {
            slice::from_raw_parts((top - 5) as *const _, 10)
        }),
        -1
    );
    assert_eq!(
        write(STDOUT, unsafe {
            slice::from_raw_parts((bottom - 5) as *const _, 10)
        }),
        -1
    );
    // TODO: test string located in .data section
    println!("Test write0 OK!");
    0
}

第一点,我们可以看到实际上它希望我们每次尝试访问不在权限内的地址后不是退出app而是选择阻止,并且继续运行下面的部分.

因此可以把
sys_write
更改为:

pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
    let app_range = get_current_app_range();
    let stack_range = get_user_stack_range();
    //println!("range: [{:#x}, {:#x})\n", range.0,range.1);
    let buf_pointer = buf as usize;
    //println!("buf_pointer: {:#x}\n", buf_pointer);
    if  (buf_pointer < app_range.0 || buf_pointer >= app_range.1) && (buf_pointer < stack_range.0 || buf_pointer >= stack_range.1) {
        println!("Out of range!");
        return (len as isize)
        //sys_exit(fd as i32)
    }
    match fd {
        FD_STDOUT => {
            let slice = unsafe { core::slice::from_raw_parts(buf, len) };
            let str = core::str::from_utf8(slice).unwrap();
            print!("{}", str);
            len as isize
        }
        _ => {
            panic!("Unsupported fd in sys_write!");
        }
  

此时输出为:

[rustsbi] RustSBI version 0.2.0-alpha.6
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|

[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: ssoft, stimer, sext (0x222)
[rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
[rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
[rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
[rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
[rustsbi] enter supervisor 0x80200000
[kernel] Hello, world!
[kernel] num_app = 2
[kernel] app_0 [0x80209020, 0x8020c768)
[kernel] app_1 [0x8020c768, 0x8020ffa0)
[kernel] Loading app_0
Out of range!
Out of range!
Out of range!
[kernel] Application exited with code -1
[kernel] Loading app_1
[kernel] Panicked at src/syscall/fs.rs:29 Unsupported fd in sys_write!

可以看到
app_0
触发了三次
Out of range!
,但是最后还是以
-1
为结束.

这时候怀疑这个
-1
是用户态的
println!
不能正常运行导致的.

尝试运行
make run
而不启用
TEST
.这样根据
user/Makefile
文件的这处描述,就会运行普通的app :

TEST ?= 0
ifeq ($(TEST), 0)
	APPS :=  $(filter-out $(wildcard $(APP_DIR)/test*.rs), $(wildcard $(APP_DIR)/*.rs))
else
	APPS :=  $(wildcard $(APP_DIR)/test$(TEST)*.rs)
endif
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))

看一下
hello_world
能不能正常输出.

发现如果只留下
hello_world.rs
一个文件还是不能正常输出.

发现
ch2-lab

console.rs
的实现方式和我们之前的不一样.但是难道这样编译出来的指针就不在用户栈或者app加载的地方了吗?这里存疑.

尝试把测例移植到我们自己的代码里边.把
test1_write0.rs

test1_write1.rs
拷贝到
user/src/bin
下面,在
user
下运行
make build
.

运行之后发现
test1_write0
里第一个错误报了
out of range!
但是第二个没导致断言
assert_eq!
卡住程序,这时候具体看两次报错是什么情况,可以发现第一个尝试是在
0x0
写入,第二个是在距离栈顶大小为
5
的位置写入大小为
10
的数据,这提醒我们增加一个对于帧尾的判断:

/// write buf of length `len`  to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
    let app_range = get_current_app_range();
    let stack_range = get_user_stack_range();
    // println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
    // println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
    let buf_begin_pointer = buf as usize;
    let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
    // println!("buf_pointer: {:#x}", buf_pointer);
    if  ((buf_begin_pointer < app_range.0 || buf_begin_pointer >= app_range.1) && 
        (buf_begin_pointer < stack_range.0 || buf_begin_pointer >= stack_range.1))||
        ((buf_end_pointer < app_range.0 || buf_end_pointer >= app_range.1) &&
        (buf_end_pointer  < stack_range.0 || buf_end_pointer  >= stack_range.1))
    {
        println!("out of range!");
        return -1 as isize;
        // sys_exit(fd as i32)
    }
    match fd {
        FD_STDOUT => {
            let slice = unsafe { core::slice::from_raw_parts(buf, len) };
            let str = core::str::from_utf8(slice).unwrap();
            print!("{}", str);
            len as isize
        }
        _ => {
            panic!("Unsupported fd in sys_write!");
        }
    }
}

这时候再次运行,发现第三次尝试报错,原因是它试图在距离栈底位置距离为
5
的地方写入
10
的数据,这下不管是帧头还算帧尾都在范围内了,那么为什么会是一个测试例子呢(
默认是错误的例子
),因为距离栈底为
5
的位置不在栈里,而后边
5
个单位在栈里,这样就导致了对于全局数据位置和临时数据位置的跨越写入,这时候把代码改成:

/// write buf of length `len`  to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
    let app_range = get_current_app_range();
    let stack_range = get_user_stack_range();
    // println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
    // println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
    let buf_begin_pointer = buf as usize;
    let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
    // println!("buf_begin_pointer: {:#x}", buf_begin_pointer);
    // println!("buf_end_pointer: {:#x}", buf_end_pointer);
    if !(
            (buf_begin_pointer >= app_range.0 && buf_begin_pointer < app_range.1) && 
            (buf_end_pointer >= app_range.0 && buf_end_pointer < app_range.1)
        )&&
        !(
            (buf_begin_pointer >= stack_range.0 && buf_begin_pointer < stack_range.1) && 
            (buf_end_pointer >= stack_range.0 && buf_end_pointer < stack_range.1)
        )
    {
        println!("out of range!");
        return -1 as isize;
        // sys_exit(fd as i32)
    }
    match fd {
        FD_STDOUT => {
            let slice = unsafe { core::slice::from_raw_parts(buf, len) };
            let str = core::str::from_utf8(slice).unwrap();
            print!("{}", str);
            len as isize
        }
        _ => {
            panic!("Unsupported fd in sys_write!");
        }
    }
}

这时候的运行结果是:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] num_app = 7
[kernel] app_0 [0x8020b048, 0x8020c370)
[kernel] app_1 [0x8020c370, 0x8020d740)
[kernel] app_2 [0x8020d740, 0x8020ece8)
[kernel] app_3 [0x8020ece8, 0x802100a0)
[kernel] app_4 [0x802100a0, 0x80211450)
[kernel] app_5 [0x80211450, 0x80212d80)
[kernel] app_6 [0x80212d80, 0x802147c0)
[kernel] Loading app_0
Hello, world!
[kernel] Application exited with code 0
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_2
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_5
out of range!
out of range!
out of range!
Test write0 OK!
[kernel] Application exited with code 0
[kernel] Loading app_6
== Begin stack trace ==
0x000000008020149a, fp = 0x0000000080206d40
0x0000000080201c02, fp = 0x0000000080206d80
0x000000008020073c, fp = 0x0000000080206e00
0x0000000080201790, fp = 0x0000000080206ef0
0x0000000080200d8c, fp = 0x0000000080209fc0
0x0000000080400032, fp = 0x000000008020a000
0x0000000000000000, fp = 0x0000000000000000
== End stack trace ==
make: *** [Makefile:64: run-inner] Error 255

第一个测试用例通过了,但是第二个还没有.果然如此,又要继续踩坑.

可以看到是加载之后就出现了BUG,而且报了调用栈,说明是触发了
panic!
.

查看代码,原来是进入了
sys_write

default
的分支,我们试着不报
panic
,把这句注释掉:

/// write buf of length `len`  to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
    let app_range = get_current_app_range();
    let stack_range = get_user_stack_range();
    // println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
    // println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
    let buf_begin_pointer = buf as usize;
    let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
    // println!("buf_pointer: {:#x}", buf_pointer);
    if !(
            (buf_begin_pointer >= app_range.0 && buf_begin_pointer < app_range.1) && 
            (buf_end_pointer >= app_range.0 && buf_end_pointer < app_range.1)
        )||
        (
            (buf_begin_pointer >= stack_range.0 && buf_begin_pointer < stack_range.1) && 
            (buf_end_pointer >= stack_range.0 && buf_end_pointer < stack_range.1)
        )
    {
        println!("out of range!");
        return -1 as isize;
        // sys_exit(fd as i32)
    }
    match fd {
        FD_STDOUT => {
            let slice = unsafe { core::slice::from_raw_parts(buf, len) };
            let str = core::str::from_utf8(slice).unwrap();
            print!("{}", str);
            len as isize
        }
        _ => {
            -1 as isize
            //panic!("Unsupported fd in sys_write!");
        }
    }
}

OK,就此解决了:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] num_app = 7
[kernel] app_0 [0x8020b048, 0x8020c370)
[kernel] app_1 [0x8020c370, 0x8020d740)
[kernel] app_2 [0x8020d740, 0x8020ece8)
[kernel] app_3 [0x8020ece8, 0x802100a0)
[kernel] app_4 [0x802100a0, 0x80211450)
[kernel] app_5 [0x80211450, 0x80212d80)
[kernel] app_6 [0x80212d80, 0x802147c0)
[kernel] Loading app_0
Hello, world!
[kernel] Application exited with code 0
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_2
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_5
out of range!
out of range!
out of range!
Test write0 OK!
[kernel] Application exited with code 0
[kernel] Loading app_6
string from data section
strinstring from stack section
strin
Test write1 OK!
[kernel] Application exited with code 0
All applications completed!

这里不要因为解决了就忘记解读第二个测例
test1_write1.rs
,查看它的源码,发现它分别测试的是:

  1. 能否跳过未支持的
    fd
  2. 能否访问
    1. 存在
      data
      段的静态变量
    2. 存在
      stack
      段的局部变量
    3. 存在
      stack
      段的局部变量的部分指针
#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;
use user_lib::write;
const DATA_STRING: &str = "string from data section\n";

const STDOUT: usize = 1;

/// 正确输出:
/// string from data section
/// strinstring from stack section
/// strin
/// Test write1 OK!

#[no_mangle]
pub fn main() -> i32 {
    assert_eq!(write(1234, DATA_STRING.as_bytes()), -1);
    assert_eq!(
        write(STDOUT, DATA_STRING.as_bytes()),
        DATA_STRING.len() as isize
    );
    assert_eq!(write(STDOUT, &DATA_STRING.as_bytes()[..5]), 5);
    let stack_string = "string from stack section\n";
    assert_eq!(
        write(STDOUT, stack_string.as_bytes()),
        stack_string.len() as isize
    );
    assert_eq!(write(STDOUT, &stack_string.as_bytes()[..5]), 5);
    println!("\nTest write1 OK!");
    0
}

问答作业

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

这里可以直接看我们在上一部分的运行结果:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] num_app = 5
[kernel] app_0 [0x8020a038, 0x8020b360)
[kernel] app_1 [0x8020b360, 0x8020c730)
[kernel] app_2 [0x8020c730, 0x8020dcd8)
[kernel] app_3 [0x8020dcd8, 0x8020f090)
[kernel] app_4 [0x8020f090, 0x80210440)
[kernel] Loading app_0
Hello, world!
[kernel] Application exited with code 0
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_2
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
All applications completed!

出错行为由
trap_handler
分类和处理:

#[no_mangle]
/// handle an interrupt, exception, or system call from user space
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
    let scause = scause::read(); // get trap cause
    let stval = stval::read(); // get extra value
    match scause.cause() {
        Trap::Exception(Exception::UserEnvCall) => {
            cx.sepc += 4;
            cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize;
        }
        Trap::Exception(Exception::StoreFault) | Trap::Exception(Exception::StorePageFault) => {
            println!("[kernel] PageFault in application, kernel killed it.");
            run_next_app();
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            println!("[kernel] IllegalInstruction in application, kernel killed it.");
            run_next_app();
        }
        _ => {
            panic!(
                "Unsupported trap {:?}, stval = {:#x}!",
                scause.cause(),
                stval
            );
        }
    }
    cx
}

把其中报错的部分挑出来.

app_1:

[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.

其源码:

#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;

#[no_mangle]
fn main() -> i32 {
    println!("Into Test store_fault, we will insert an invalid store operation...");
    println!("Kernel should kill this application!");
    unsafe {
        core::ptr::null_mut::<u8>().write_volatile(0);
    }
    0
}

可以看到它是尝试写入了一个空指针.这时候就会触发
trap_handler
里的
Trap::Exception(Exception::StoreFault)
,意为访问了无效的内存地址.

app_3:

[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.

其源码:

#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;

use core::arch::asm;

#[no_mangle]
fn main() -> i32 {
    println!("Try to execute privileged instruction in U Mode");
    println!("Kernel should kill this application!");
    unsafe {
        asm!("sret");
    }
    0
}

可见它是尝试调用
sret
,这是需要S特权级的.因此触发
trap_handler

Trap::Exception(Exception::IllegalInstruction)
,意为使用了非法指令.

app_4:

[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.

其源码:

#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;

use riscv::register::sstatus::{self, SPP};

#[no_mangle]
fn main() -> i32 {
    println!("Try to access privileged CSR in U Mode");
    println!("Kernel should kill this application!");
    unsafe {
        sstatus::set_spp(SPP::User);
    }
    0
}

可见它尝试写入
sstatus
,也即尝试访问
S

CSR
,因此触发
trap_handler

Trap::Exception(Exception::IllegalInstruction)
,意为使用了非法指令,但是这里不同的是用指令访问了
S
特权级才能访问的寄存器.

SBI的版本在
os/Cargo.toml
可以看到:

sbi-rt = { version = "0.0.2", features = ["legacy"] }

2. 请结合用例理解
trap.S
中两个函数
__alltraps

__restore
的作用,并回答如下几个问题:

这里是
trap.S
的内容:

.altmacro
.macro SAVE_GP n
    sd x\n, \n*8(sp)
.endm
.macro LOAD_GP n
    ld x\n, \n*8(sp)
.endm
    .section .text
    .globl __alltraps
    .globl __restore
    .align 2
__alltraps:
    csrrw sp, sscratch, sp
    # now sp->kernel stack, sscratch->user stack
    # allocate a TrapContext on kernel stack
    addi sp, sp, -34*8
    # save general-purpose registers
    sd x1, 1*8(sp)
    # skip sp(x2), we will save it later
    sd x3, 3*8(sp)
    # skip tp(x4), application does not use it
    # save x5~x31
    .set n, 5
    .rept 27
        SAVE_GP %n
        .set n, n+1
    .endr
    # we can use t0/t1/t2 freely, because they were saved on kernel stack
    csrr t0, sstatus
    csrr t1, sepc
    sd t0, 32*8(sp)
    sd t1, 33*8(sp)
    # read user stack from sscratch and save it on the kernel stack
    csrr t2, sscratch
    sd t2, 2*8(sp)
    # set input argument of trap_handler(cx: &mut TrapContext)
    mv a0, sp
    call trap_handler

__restore:
    # case1: start running app by __restore
    # case2: back to U after handling trap
    mv sp, a0
    # now sp->kernel stack(after allocated), sscratch->user stack
    # restore sstatus/sepc
    ld t0, 32*8(sp)
    ld t1, 33*8(sp)
    ld t2, 2*8(sp)
    csrw sstatus, t0
    csrw sepc, t1
    csrw sscratch, t2
    # restore general-purpuse registers except sp/tp
    ld x1, 1*8(sp)
    ld x3, 3*8(sp)
    .set n, 5
    .rept 27
        LOAD_GP %n
        .set n, n+1
    .endr
    # release TrapContext on kernel stack
    addi sp, sp, 34*8
    # now sp->kernel stack, sscratch->user stack
    csrrw sp, sscratch, sp
    sret

1. L40:刚进入
__restore
时,
a0
代表了什么值。请指出
__restore
的两种使用情景。

刚刚进入
__restore
的时候是
a0
是我们传入
__restore
的参数,我们在
run_next_app
中调用了这个函数:

/// run next app
pub fn run_next_app() -> ! {
    let mut app_manager = APP_MANAGER.exclusive_access();
    let current_app = app_manager.get_current_app();
    unsafe {
        app_manager.load_app(current_app);
    }
    app_manager.move_to_next_app();
    drop(app_manager);
    // before this we have to drop local variables related to resources manually
    // and release the resources
    extern "C" {
        fn __restore(cx_addr: usize);
    }
    unsafe {
        __restore(KERNEL_STACK.push_context(TrapContext::app_init_context(
            APP_BASE_ADDRESS,
            USER_STACK.get_sp(),
        )) as *const _ as usize);
    }
    panic!("Unreachable in batch::run_current_app!");
}

可以看到传入的是我们主动制造的
TrapContext
的指针,
TrapContext
内容是APP加载位置
APP_BASE_ADDRESS
和用户栈的指针.

它被调用的情景分为两种:

  1. 一个APP运行结束或者出错之后的APP切换
  2. 在内核工作之后开始APP的加载和运行

2. L46-L51:这几行汇编代码特殊处理了哪些寄存器?这些寄存器的的值对于进入用户态有何意义?请分别解释。

代码在此:

    # restore sstatus/sepc
    ld t0, 32*8(sp)
    ld t1, 33*8(sp)
    ld t2, 2*8(sp)
    csrw sstatus, t0
    csrw sepc, t1
    csrw sscratch, t2

这一部分是先把存在栈中的内容转移到
t0~t2
,然后再把它们还原回
sstatus
,
sepc

sscratch
.

这里不能直接移动的原因是
ld
可以操作寄存器的指针偏移,而
csrw
只能操作寄存器和寄存器.

sstatus
:
SPP
等字段给出 Trap 发生之前 CPU 处在哪个特权级(S/U)等信息.

sepc
:当 Trap 是一个异常的时候,记录 Trap 发生之前执行的最后一条指令的地址

sscratch
:是保存
Trap
时的
sscratch
的值,实际上是当时的用户栈的指针.

后续调用
csrrw sp, sscratch, sp
使得
sp
被交换回用户栈的指针.

3. L53-L59:为何跳过了
x2

x4

官方文档
:

  1. 但这里也有一些例外,如
    x0
    被硬编码为 0 ,它自然不会有变化;还有
    tp(x4)
    寄存器,除非我们手动出于一些特殊用途使用它,否则一般也不会被用到。
  2. 我们在这里也不保存 sp(x2),因为我们要基于它来找到每个寄存器应该被保存到的正确的位置。

4. L63:该指令之后,
sp

sscratch
中的值分别有什么意义?

此指令后,sp->user stack sscratch->kernel stack,因此
sp
重新指向用户栈.

5.
__restore
:中发生状态切换在哪一条指令?为何该指令执行之后会进入用户态?

发生状态切换是在
sret
.

硬件上是这个原因,调用它之后寄存器会发生变化.

6. L13:该指令之后,
sp

sscratch
中的值分别有什么意义?

这个指令之后就把
sp

sscratch
切换了,就会导致sp->kernel stack, sscratch->user stack.

7. 从 U 态进入 S 态是哪一条指令发生的?

应该是再用户态的
ecall
指令发生的.

3. 对于任何中断,
__alltraps
中都需要保存所有寄存器吗?你有没有想到一些加速
__alltraps
的方法?简单描述你的想法。

只需要保存中断过程中可能需要改变的寄存器.

这里可提前读取
scause
的内容,根据情况来处理,以本章
trap_handler
为例:

#[no_mangle]
/// handle an interrupt, exception, or system call from user space
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
    let scause = scause::read(); // get trap cause
    let stval = stval::read(); // get extra value
    match scause.cause() {
        Trap::Exception(Exception::UserEnvCall) => {
            cx.sepc += 4;
            cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize;
        }
        Trap::Exception(Exception::StoreFault) | Trap::Exception(Exception::StorePageFault) => {
            println!("[kernel] PageFault in application, kernel killed it.");
            run_next_app();
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            println!("[kernel] IllegalInstruction in application, kernel killed it.");
            run_next_app();
        }
        _ => {
            panic!(
                "Unsupported trap {:?}, stval = {:#x}!",
                scause.cause(),
                stval
            );
        }
    }
    cx
}

当检测到
scause

Environment call from U-mode
的时候进行相应的调用,因此需要保存上下文,而遇到
Store access fault

Illegal instruction
的时候则不需要保存.

这里是
mcause
的位描述,
scause
是它的一个子集,是一样的布局:

标签: none

添加新评论