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.bss
After paragraph ...
Pay attention here.sp
register is the stack pointer register, thefp
The registers are not the same as the frame pointer registers.
Here's a rehash.os/.cargo/config
of 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 thefp
value (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 the
As a submodule.
mod stack_trace;
compilerlang_items.rs
, inpanic
function by adding theunsafe { print_stack_trace(); }
.
Note the addition of the dependencyuse crate::stack_trace::print_stack_trace;
existAppManager
function ofload_app
The 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 theos
directorymake 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 mentionedtrap
Process.
-
print!
->print
->Stdout.write_fmt
->write
->sys_write
->syscall
Yes, it is.user
layer of the function call chain. -
syscall
->sys_write
->print!
->Stdout.write_fmt
->console_putchar
Yes, it is.os
layer of the function call chain. - utilization
x10~x17
As 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-sbi
Of 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, themideleg
respond 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 debug
go intodebug
mode, using the GDB commandbreak rust_main
breakpoints, and then use thec
command causes the program to stop atrust_main
Before.
Using commandsinfo register mideleg
respond in singinginfo register medeleg
The 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.mideleg
The effective number of digits is an odd number of digits, and we should let the0x666
compatibility with anb1010 1010 1010
, and what you get is0x222
I've got it.
Similarly formedeleg
(used form a nominal expression)0xf0b5ff
It should be taken0~15
vacancy10
cap (a poem)14
Bit, I think.compatibility with anb1011 1011 1111 1111
capture0xb1ff
It 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
->sret
For 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.
Trap
The 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 thesret
But 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 aboutTrap
Returned 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 seenTrap
The return instruction for thesret
respond 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 ofTrapContext
Definition 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, thatTrap
context is the context that the CSR keeps about theTrap
and general-purpose registers.
If it is not possible to carry outTrap
context 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_write
This 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 ofAppManager
Create a method to get the current app address, and because every time you load the app after thecurrent_app
The 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_range
I then realized that APP2 was always reporting errors, and I put thesys_exit
exchange (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.sp
I 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-v3
Go 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
sayrustc
The 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.rust
After 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 seenset
channel
Affects 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_0
is triggered during the operation of theApplication exited with code 1
It's not working as expected. Andapp_1
It's more of a heavyweight jump.
Since we can program for results, I'm going to take a look at it myself.app_0
What 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_write
Replace 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_0
It's triggered three times.Out of range!
But in the end, it's all about-1
To conclude.
At this point it's doubtful that this-1
userlandprintln!
It's not working properly.
Try runningmake run
without enablingTEST
. In this way, according to theuser/Makefile
file 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_world
Can it be output properly.
It was found that if only the lefthello_world.rs
A 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_write0
The 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 the0x0
The second one is written at a distance from the top of the stack of size5
Write the position of the location with the size of10
data, 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 of5
place to write the10
Now 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 is5
is not on the stack, and the backside5
This 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)default
branch, 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.
- Can you skip the unsupported
fd
- Accessibility
- remain
data
Static variables for paragraphs - remain
stack
Local Variables for Segments - remain
stack
Partial pointer to a local variable in a segment
- remain
#![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_handler
Classification 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_handler
innerTrap::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 callsret
This 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 theS
Privileged 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__restore
when it isa0
It's our incoming__restore
parameter in therun_next_app
This 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 usTrapContext
pointer to theTrapContext
Content is where the app loadsAPP_BASE_ADDRESS
and the pointer to the user stack.
It is called in two scenarios.
- App switching after an app has finished running or an error has occurred
- 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
,sepc
cap (a poem)sscratch
.
The reason you can't move directly here isld
You can manipulate pointer offsets of registers, and thecsrw
Only 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 ofTrap
currentsscratch
value, which is actually a pointer to the user stack at that time.
successor callcsrrw sp, sscratch, sp
feasiblesp
Pointer to be swapped back to the user stack.
3. L53-L59: Why were they skipped?x2
cap (a poem)x4
?
official document:
- But there are some exceptions here, such as
x0
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. - 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, thussp
Redirects 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 thesp
respond in singingsscratch
Toggled, 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.ecall
The 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 herescause
of the chapter, as the case may be.trap_handler
Example.
#[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 thescause
beEnvironment call from U-mode
is called accordingly, so the context needs to be preserved, and aStore access fault
cap (a poem)Illegal instruction
If you do not want to save it, you don't need to save it.
Here it is.mcause
bit descriptions, thescause
is a subset of it, is the same layout.