Location>code7788 >text

[rCore Study Notes 020] Chapter 2 Assignment

Popularity:576 ℃/2024-08-01 16:06:16

write sth. upfront

This essay was written by a very rookie rookie. Please feel free to ask any questions.

You can contact: 1160712160@

GitHhub:/WindDevil (Nothing so far.

programming question

Implement a bare-metal application A that can print the call stack

The first thing that stuck me here for a long time is where the call stack is saved, back to the last part of the drawing, it's actually in the.bssAfter paragraph ...

Pay attention here.spregister is the stack pointer register, thefpThe registers are not the same as the frame pointer registers.

Here's a rehash.os/.cargo/configof the content, the"-Cforce-frame-pointers=yes"Represents the save stack pointer.

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

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

At this point it is possible to pass thefpvalue (to ensure that it is not on the user or kernel stack during a trap).

At this point it is possible to createos/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 = *(1);
        let saved_fp = *(2);

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

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

existAdd it to theAs a submodule.

mod stack_trace;

compilerlang_items.rs, inpanicfunction by adding theunsafe { print_stack_trace(); }.

Note the addition of the dependencyuse crate::stack_trace::print_stack_trace;

existAppManagerfunction ofload_appThe portion of all apps that have finished running pluspanic!("Shutdown machine!");, instead of the original normal shutdown.

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,  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!("");
    }

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

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

Then directly in theosdirectorymake run. If an error is reported

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

Just comment it out.use crate::sbi::shutdown;can be...

This gives the result.

[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

Subsequent topics

question and answer

1. What is the difference between a function call and a system call?

  • Function calls are made with ordinary control-flow instructions and do not involve a privilege-level switch; system calls are made with specialized instructions (e.g., ecall on RISC-V) that switch to the kernel privilege level.
  • A function call can specify the target of the call at will; a system call can only switch the control flow to the target given by the calling operating system kernel.

Here note go to the previously mentionedtrapProcess.

  1. print! -> print -> Stdout.write_fmt -> write -> sys_write -> syscall Yes, it is.userlayer of the function call chain.
  2. syscall -> sys_write -> print! -> Stdout.write_fmt -> console_putchar Yes, it is.oslayer of the function call chain.
  3. utilizationx10~x17As a bridge between

2. The M-state software delegates S-state exceptions/interrupts to the S-state software in order to facilitate handling by the operating system. point out which registers record the delegated information, and which exceptions/interrupts have been delegated by rustsbi? (You can also give the values of the registers directly)

The problem is mainly in therust-sbiOf course, this question can be answered by asking the GPT directly, but in order to enhance our querying ability, if the GPT doesn't know or we need more accurate and standardized information, we can check theRISC-V Manual (), searching for its contents.

We search for commissions in it, and we see that10.5 The Regulator Model for Modern Operating Systems A description of RISC-V's delegation mechanism is mentioned in this chapter.
By default, control of all exceptions, regardless of privilege mode, is transferred to the M-mode exception handler. However, most exceptions in a Unix system should be called from the system in S-mode. the M-mode exception handler could redirect the exception to S-mode, but these additional operations would slow down the handling of most exceptions. Therefore, RISC-V provides an exception delegation mechanism. This mechanism allows interrupt and synchronization exceptions to be optionally delegated to S-mode, bypassing M-mode altogether.

Two CSRs are mentioned here, themidelegrespond in singingmedeleg, interrupts and synchronization exceptions, respectively, can be delegated to S-mode.
mideleg (Machine Interrupt Delegation) The CSR controls which interrupts are delegated to the S mode.
M-mode can also delegate synchronization exceptions to S-mode via medeleg CSR.

Synchronization exceptions may have a strange name that makes people think there are other types of exceptions, but there are actually only two types of exceptions: synchronization exceptions and interrupts.
RISC-V divides exceptions into two categories. One is synchronous exceptions, which are generated during instruction execution, such as when an invalid memory address is accessed or an instruction with an invalid opcode is executed. The other category is interrupts, which are external events that are asynchronous to the instruction flow, such as a mouse click.

Note that control is not handed over to a mode with lower privileges when an exception occurs, regardless of the delegation settings. Exceptions that occur in M mode are always handled in M mode. Exceptions that occur in S mode may be handled by either M mode or S mode, depending on the specific delegation settings, but never by U mode.

The figure contains descriptions of interrupts and synchronization exceptions, and the first half of the table is about thedisruptions The second half of the table is aboutsynchronization exception The following is an example of an exception that can be delegated to S-mode. Setting the corresponding bit high will delegate the exception to S-mode.

For example, mideleg[5] corresponds to an S-mode clock interrupt, and if it is set, the S-mode clock interrupt will be handed over to the S-mode exception handler instead of the M-mode exception handler.

For example, placing a medeleg [15] delegates store page faults (missing pages that occur during the store process) to the S-mode.

utilizationmake debuggo intodebugmode, using the GDB commandbreak rust_mainbreakpoints, and then use theccommand causes the program to stop atrust_mainBefore.

Using commandsinfo register midelegrespond in singinginfo register medelegThe values of two registers can be read.

mideleg        0x666    1638
medeleg        0xf0b5ff 15775231 

I realized that it doesn't match the answer, but if it doesn't, then it's right. Look at the chart above, actually.midelegThe effective number of digits is an odd number of digits, and we should let the0x666 compatibility with anb1010 1010 1010, and what you get is0x222I've got it.

Similarly formedeleg(used form a nominal expression)0xf0b5ffIt should be taken0~15vacancy10cap (a poem)14Bit, I think.compatibility with anb1011 1011 1111 1111capture0xb1ffIt seems to be inconsistent with the answer here, but we can justify it, so let's leave it at that.

3. If the operating system exists in the form of an application library, in what ways can an application program damage the operating system?

This question focuses on comparing the differences between the operating systems in Chapter 1 and Chapter 2, and finding problems with the operating systems in Chapter 1.

This first impression is mainly what we'll be working on in Chapter 2, which is the issue of calling higher-level instructions. But by calling higher-level instructions, we can ensure that our operations are already defined if we use the operating system to intercept those instructions, and we won't have problems.

All the weaknesses of this question, in turn, are all the strengths of chapter two. The answer is perfect.

If the operating system exists in the form of application libraries, then the compiler will link the application with the OS library into an executable file when linking OS libraries, and both of them are in the same address space, which is also the LibOS (Unikernel) architecture, and at this time, there are several ways to destroy the operating system as follows:

  • Buffer overflow: an application can overwrite writes outside its legal memory boundaries, which can jeopardize the OS;
  • Integer overflow: An integer overflow occurs when an operation on an integer value produces a value that is outside the range that can be represented by the integer data type, which can lead to unexpected behavior and security vulnerabilities in the OS. For example, if an application is allowed to allocate a large amount of memory, an attacker could trigger an integer overflow during a memory allocation routine, which could lead to a buffer overflow or other security vulnerabilities;
  • System Call Interception: Applications may intercept or redirect system calls that may compromise the behavior of the OS. For example, an attacker may intercept system calls that read sensitive files and redirect them to a file of their choice, potentially compromising the security of the unikernel.
  • Resource exhaustion: Applications may consume resources such as memory or network bandwidth, which may lead to denial of service or other security breaches.

4. How do compilers/operating systems/processors work together, and what methods can be used to protect the operating system from being corrupted by applications?

Still referring to the official documentation for the reference answer.

Hardware operating systems run in a hardware-protected, secure execution environment that is not compromised by applications; applications run in another restricted execution environment that cannot compromise the operating system. Modern CPUs provide many hardware mechanisms to protect the operating system from malicious applications, including the following:

  • Privileged Level Mode: The processor is able to set up execution environments with different security levels, i.e. user-state execution environment and kernel-state privileged level execution environment. The processor performs a privilege-level security check before executing an instruction. If a kernel privilege-level instruction is executed in the user-state execution environment, an exception will be generated to prevent the execution of the current illegal instruction.
  • TEE (Trusted Execution Environment): The CPU's TEE enables the construction of a trusted execution environment that can be used to defend against malware or attacks, and can secure applications that handle sensitive data, such as mobile banking and payment applications.
  • ASLR (Address Space Layout Randomization): ASLR is a CPU security feature that randomizes the address space layout of a process. It is capable of randomly generating the starting address of critical parts of the process address space, such as the stack, shared libraries, and so on, allowing an attacker to predict the location of specific data or code.

5. What are the S-state privileged instructions of a RISC-V processor, what are their general meanings, and what do they do?

We've talked about its categorization before, and you should consult the bibliography for specific S-state privileged instructions.RISC-V Manual ().

The userland software executes special commands to obtain the service functions of the kernel operating system.
1. The instruction itself belongs to a highly privileged class of instructions, such assret Command (indicates return from S-mode to U-mode)
2. The command accessedRegisters accessible only at the S-mode privilege level or memory, such as the one that indicates the state of the S-mode systemControl Status Register sstatus et al. (and other authors)

By consulting the manual we can conclude that the privileged instructions are categorized as shown below:

The role of sret:

The role of mret:

The role of wfi:

The role of the

To access the CSRs, here is a direct list of CSRs in M-mode, and the CSRs in S-mode are named by putting the name of them, replaced bys

There are also some CSR-specific commands.

The CSR-related instructions in the figure are.

6. What is the hardware-level processing of a RISC-V processor after the execution of privileged instructions in the user state?

Including the above part of the drawing is never about how to do privilege level switching, but we always ignore the CSR changes in the switching context, these parts of the think about it is through the hardware to change, this problem is just a very appropriate reminder for us.

When the CPU finishes executing an instruction (e.g., ecall) and is ready to tramp from the user privilege level to the S privilege level, the hardware automatically does the following:

  • The SPP field of sstatus is modified to the CPU's current privilege level (U/S).
  • sepc is modified to be the address of the next instruction that will be executed by default after Trap processing is complete.
  • scause/stval will be changed to the reason for the Trap and additional information about it, respectively.
  • The cpu jumps to the Trap processing entry address set by stvec, sets the current privilege level to S, and then begins execution at the Trap processing entry address

When the CPU completes the Trap processing and is ready to return, it needs to do so by means of an S privilege level privileged instruction sret, which accomplishes the following functions: * The CPU will set the current privilege level to U or S according to the SPP field of sstatus; * The CPU will jump to the instruction pointed to by the sepc register, and then continue execution.

7. What is the general process by which an operating system completes a user-state <-> kernel-state bidirectional switch?

This is more detailed in the green section of the previous section onecall->x10~x17->trap_handler->sretFor a description of this section, take a look at the big picture in (019 Testing the chapter implementation in main).

When the CPU runs an application program at the user-state privileged level (U mode of RISC-V), executes to the Trap, switches to the kernel-state privileged level (S mode of RISC-V), the corresponding code of the batch operating system responds to the Trap and executes the system call service, and after the processing is complete, it returns from the kernel-state to the user-state application program to continue executing subsequent instructions.

8. Programs can get into the kernel due to interrupts, exceptions and traps (system calls), what interrupts/exceptions does riscv64 support? How can you tell if the entry into the kernel is due to an interrupt or an exception? Describe a few important registers and their values when you get stuck in the kernel.

Here's where it's still important to look at this picture

To determine the current interrupt and exception conditions just look at thescause,RISC-V Manual () The description of it is.
scause is set according to Figure 10.3 according to the exception type, and stval is set to the address of the error or other information word for the specific exception.

TrapThe important registers are also shown in this figure, the list of CSRs in M-mode is listed here directly, and the CSRs in S-mode are named by putting the name inm, replaced bys:

9. Under what circumstances does a privilege level switch occur: user-state->kernel-state, and kernel-state->user-state?

The user-state to kernel-state switch has been repeated many times: the

Some anomaly in the execution of the higher-level software or thespecial case , need to use the functions provided in the implementation environment
1. As you can see here, although they are all calledexceptions However, there are actually some special cases that require the use of functions in the execution environment, so you can't just put theexceptions see it ascorruptible
2. The causes of exceptions triggered directly from the user state to the kernel state by a user-state application can be categorized into two general categories
1. One is the execution of special commands by the userland software to obtain the service functions of the kernel operating system.
1. The instruction itself belongs to a highly privileged class of instructions, such assret Command (indicates return from S-mode to U-mode)
2. The command accessedRegisters accessible only at the S-mode privilege level or memory, such as the one that indicates the state of the S-mode systemControl Status Register sstatus et al. (and other authors)
2. The second is that an error is generated during the execution of an instruction (e.g., execution of an instruction that is not allowed to be executed by the user state or other errors) and is detected by the CPU.

But from kernel to user, the only thing we can think of right away is thesretBut think about this chapter of our implementation of the operating system is when to start running APP it, running how to enter the user state it, more specific and more detailed does not seem to have?

Think about the last part where we wereOfficial HandbookAs you can see in.
The only way to bring down the CPU privilege level is to execute the privileged instructions returned by Trap, such assret 、mret et al. (and other authors)

That's when we go back toRISC-V Manual () Find out more aboutTrapReturned Privileged Directives.
The supervisor exception return instruction sret has the same behavior as mret, but it acts on S-mode exception handling CSRs instead of M-mode CSRs.

can be seenTrapThe return instruction for thesretrespond in singingmret.

Here's a repeat of what they both do.

The role of sret:

The role of mret:

10. What does the Trap context mean? What are the specifics of the Trap context in the operating system in this chapter? What happens if you don't save and restore the Trap context?

This is directly linked to our understanding ofTrapContextDefinition code for.

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

Here we can recall, in turn, thatTrapcontext is the context that the CSR keeps about theTrapand general-purpose registers.

If it is not possible to carry outTrapcontext preservation and restoration, the CPU will not be able to correctly restore to the original privilege level.

Laboratory exercises

practical work

sys_write security check

In ch2, we implement the first system callsys_writeThis allows us to output information in the userland. But while os provides services, it also protects os itself and other user programs from bugs or malicious programs.

Since virtual memory has not yet been implemented, we can specify a user program that belongs to another program string and output it, which is obviously not reasonable, so we have to do a check on sys_write:

  • sys_write can only output data located in the program's own memory space, otherwise an error is reported.

because ofAppManagerCreate a method to get the current app address, and because every time you load the app after thecurrent_appThe number plus one, so the range to be read isself.app_start[self.current_app]~self.app_start[self.current_app-1], calculate the APP size as an offset, and then just calculate it through the base pointer.

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,  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!("");
    }

    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()
}

existCreates a method in the user stack that calculates the range of the user stack.

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

remodelsys_wirte, if the variable is not in one of the two ranges above, then thesys_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!");
        }
    }
}

There's actually a little pit-stepping going on here during the writing process (all the way to the pit all the way to G).

At first, I only thought that the value of the parameter had to be in the range of the loaded value, which is what I mentioned above.app_rangeI then realized that APP2 was always reporting errors, and I put thesys_exitexchange (sth) for (sth else)println!, and found that each timeAccess Variables It all goes wrong, and that's when the question of synonymity is asked.spI was struck by the following sentence.
The stack pointer register is used to keep track of the current location of the top of the stack. Whenever a function call occurs, arguments and local variables are pressed onto the stack and the stack pointer is moved down accordingly (in RISC-V, the stack grows downward).

will recall the firm Qiu 51 microcontroller experiments, he always said to save the scene to save the scene, in fact, to save the local variables, local variables are present in the general-purpose registers! (I don't know if I've got it all figured out.)

This part was added by referring to someone else's code.

When testing, you need to put your own changes into the official code:~/App/rCore-Tutorial-v3Go inside.git checkout ch2-lab, Runningmake run TEST=1.

Here's the error when it runs.

(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

sayrustcThe level is too low. Update it.rustc, and then install the dependency.

rustup update

Found to be ineffective.

But here's a new one. Update.rustAfter that to see the latest version you need toRestart the command line.

It remains ineffective because.

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/')
rustc 1.64.0-nightly (9a7b7d5e5 2022-07-19)

can be seensetchannelAffects our versioning issues.

Go in and change it to the latestchannel = "nightly-2024-07-30"That's it.

But this creates a new conflict.

warning: the feature `panic_info_message` has been stable since 1.82.0-nightly and no longer requires an attribute to enable
 --> src/: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

comment outApp/rCore-Tutorial-v3/user/src/(used form a nominal expression)#![feature(panic_info_message)].

Take all thepanic_info.message().unwarp()adapt (a story to another medium)panic_info.message().as_str().unwrap_or("no message")That's it.

Output when not modified.

[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/:45 no message

You can see that loading app0 brings up thepanic.

After modification.

[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/:27 Unsupported fd in sys_write!

can be seenapp_0is triggered during the operation of theApplication exited with code 1It's not working as expected. Andapp_1It's more of a heavyweight jump.

Since we can program for results, I'm going to take a look at it myself.app_0What are the examples of precious things?

#![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};

/// correct output:
/// 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
}

First, we can see that it actually expects us not to exit the app every time we try to access an address that's not in our permissions, but to choose to block it and continue running the following section.

It is therefore possible to putsys_writeReplace with the following.

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!");
        }
  

The output at this point is.

[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/:29 Unsupported fd in sys_write!

can be seenapp_0It's triggered three times.Out of range!But in the end, it's all about-1To conclude.

At this point it's doubtful that this-1userlandprintln!It's not working properly.

Try runningmake runwithout enablingTEST. In this way, according to theuser/Makefilefile is described here, it will run the normal 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))

Have a look.hello_worldCan it be output properly.

It was found that if only the lefthello_world.rsA file is still not outputting properly.

discoveriesch2-lab(used form a nominal expression)The implementation is different from what we had before. But wouldn't the compiled pointer be on the user stack or where the app is loaded? I don't know.

Trying to port examples to our own code.

After runningtest1_write0The first error inout of range!But the second one didn't lead to an assertion.assert_eq!Stuck in the program, this time specifically look at the two errors is what the situation, you can find the first attempt is in the0x0The second one is written at a distance from the top of the stack of size5Write the position of the location with the size of10data, which reminds us to add a judgment on the end of the frame.

/// 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{(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!");
        }
    }
}

This time, I ran it again, and found that the third attempt reported an error, because it was trying to run the program at a distance from the bottom of the stack of5place to write the10Now both the header and the tail of the frame are in range, so why is this a test case (Default is the wrong example ), since the distance to the bottom of the stack is5is not on the stack, and the backside5This leads to writing across global data locations and temporary data locations, which changes the code to.

/// 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{(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
        }
        _ => {
            panic!("Unsupported fd in sys_write!");
        }
    }
}

The result of this run at this point is.

[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
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
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
0x0000000080200714, fp = 0x0000000080206e00
0x0000000080201790, fp = 0x0000000080206ef0
0x0000000080200d8c, fp = 0x0000000080209fc0
0x0000000080400032, fp = 0x000000008020a000
0x0000000000000000, fp = 0x0000000000000000
== End stack trace ==
make: *** [Makefile:64: run-inner] Error 255

The first test case passed, but the second one didn't. And so it goes, and so it goes.

You can see that the bug appeared after loading, and reported the call stack, indicating that it was triggered.panic!.

Now that it's triggered.panic!So let's runmake run LOG=info, so that you can see more information.

[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!
[ INFO] [kernel] .data [0x8020b000, 0x80215000)

[ WARN] [kernel] boot_stack top=bottom=0x80225000, lower_bound=0x80215000

[ERROR] [kernel] .bss [0x80225000, 0x80226000)

[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
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
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
[ERROR] [kernel] Panicked at src/syscall/:36 Unsupported fd in sys_write!

== Begin stack trace ==
0x00000000802009cc, fp = 0x0000000080206d30
0x0000000080201e00, fp = 0x0000000080206d70
0x0000000080200ecc, fp = 0x0000000080206e00
0x00000000802014b8, fp = 0x0000000080206ef0
0x0000000080200fc8, fp = 0x0000000080208fc0
0x0000000080400032, fp = 0x0000000080209000
0x0000000000000000, fp = 0x0000000000000000
== End stack trace ==
make: *** [Makefile:64: run-inner] Error 255

So it's into thesys_write(used form a nominal expression)defaultbranch, we try not to reportpanic, comment out this sentence.

/// 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{(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, that settles it.

[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!
[ INFO] [kernel] .data [0x8020b000, 0x80215000)

[ WARN] [kernel] boot_stack top=bottom=0x80225000, lower_bound=0x80215000

[ERROR] [kernel] .bss [0x80225000, 0x80226000)

[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
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
out of range!
^out of range!
=out of range!
(MOD out of range!
)
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!

Don't forget to interpret the second test case here just because it's solvedtest1_write1.rs, looking at its source code, I found that it tests respectively.

  1. Can you skip the unsupportedfd
  2. Accessibility
    1. remaindataStatic variables for paragraphs
    2. remainstackLocal Variables for Segments
    3. remainstackPartial pointer to a local variable in a segment
#![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;

/// correct output:
/// 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
}

question and answer session

1. After correctly entering the U-state, the program should also be characterized by the use of S-state privileged instructions, and by errors after accessing S-state registers. Test these on your own (run Rust's three bad quizzes), describing the program's error behavior, and noting which sbi you are using and its version.

Here's a direct look at the results of our run in the previous section: the

[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!

The wrongdoing is caused bytrap_handlerClassification and processing.

#[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 () {
        Trap::Exception(Exception::UserEnvCall) => {
             += 4;
            [10] = syscall([17], [[10], [11], [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}!",
                (),
                stval
            );
        }
    }
    cx
}

Pick out the part that says something wrong.

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.

Its source code.

#![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
}

You can see that it is trying to write a null pointer. This triggers thetrap_handlerinnerTrap::Exception(Exception::StoreFault), meaning that an invalid memory address was accessed.

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.

Its source code.

#![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
}

It can be seen that it is trying to callsretThis requires an S privilege level. So the triggertrap_handler(used form a nominal expression)Trap::Exception(Exception::IllegalInstruction), meaning that illegal instructions were used.

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.

Its source code.

#![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
}

It can be seen that it tries to writesstatus, i.e., trying to accessS(used form a nominal expression)CSR, thus triggeringtrap_handler(used form a nominal expression)Trap::Exception(Exception::IllegalInstruction), meaning that an illegal instruction was used, but the difference here is that the instruction accessed theSPrivileged access to registers.

The version of SBI is inos/You can see.

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

2. Please understand this in the context of the use cases The two functions in__alltraps cap (a poem)__restore and answer the following questions.

Here it is.Content.

.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: just entered__restore whena0 What values are represented. Please indicate__restore The two usage scenarios of the

have just entered__restorewhen it isa0It's our incoming__restoreparameter in therun_next_appThis function is called in.

/// 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!");
}

You can see that the incoming is actively made by usTrapContextpointer to theTrapContextContent is where the app loadsAPP_BASE_ADDRESSand the pointer to the user stack.

It is called in two scenarios.

  1. App switching after an app has finished running or an error has occurred
  2. Start APP loading and running after the kernel is working

2. L46-L51: What registers are specially handled in these lines of assembly code? What is the significance of the values of these registers for entering the user state? Explain each of them.

Code here.

    # 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

This part starts by transferring the contents of the presence stack to thet0~t2, and then restore them back to thesstatus,sepccap (a poem)sscratch.

The reason you can't move directly here isldYou can manipulate pointer offsets of registers, and thecsrwOnly registers and registers can be operated.

sstatus:SPP fields give information such as which privilege level (S/U) the CPU was in before the Trap occurred.

sepc: When the Trap is an exception, record the address of the last instruction executed before the Trap occurred.

sscratch:: It is the preservation ofTrapcurrentsscratchvalue, which is actually a pointer to the user stack at that time.

successor callcsrrw sp, sscratch, spfeasiblespPointer to be swapped back to the user stack.

3. L53-L59: Why were they skipped?x2 cap (a poem)x4

official document:

  1. But there are some exceptions here, such asx0 is hardcoded to 0, which is naturally unchanged; and thetp(x4) register, and it is not generally used unless we manually use it for some special purpose.
  2. We don't save sp(x2) here either, because we want to find the correct location that each register should be saved to based on it.

4. L63: After this instruction.sp cap (a poem)sscratch What is the significance of each of the values in

After this instruction,sp->user stack sscratch->kernel stack, thusspRedirects to the user stack.

5. __restore: In which instruction does the state switch occur? Why does this instruction enter the user state after execution?

The state switch occurs when thesret.

This is the reason in hardware, the registers are changed after calling it.

6. L13: After this instruction.sp cap (a poem)sscratch What is the significance of each of the values in

This command then puts thesprespond in singingsscratchToggled, it leads to sp-> kernel stack, sscratch-> user stack.

7. Which instruction occurs when you go from the U-state to the S-state?

It's supposed to be reusable.ecallThe command occurs.

3. For any interruption.__alltraps Do you need to save all the registers in all of them? Have you thought of some way to speed up the__alltraps of the method? Briefly describe your idea.

Only registers that may need to be changed during an interrupt need to be saved.

It can be read ahead herescauseof the chapter, as the case may be.trap_handlerExample.

#[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 () {
        Trap::Exception(Exception::UserEnvCall) => {
             += 4;
            [10] = syscall([17], [[10], [11], [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}!",
                (),
                stval
            );
        }
    }
    cx
}

When detecting thescausebeEnvironment call from U-modeis called accordingly, so the context needs to be preserved, and aStore access faultcap (a poem)Illegal instructionIf you do not want to save it, you don't need to save it.

Here it is.mcausebit descriptions, thescauseis a subset of it, is the same layout.