寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請及時提出。 可以聯繫:[email protected] GitHhub:https://github.com/WindDevil (目前啥也沒有 編程題 實現一個裸機應用程式A,能列印調用棧 首先在這裡卡了我很久的是調用棧保存在哪裡,回想到上一部分畫的 ...
寫在前面
本隨筆是非常菜的菜雞寫的。如有問題請及時提出。
可以聯繫:[email protected]
GitHhub:https://github.com/WindDevil (目前啥也沒有
編程題
實現一個裸機應用程式A,能列印調用棧
首先在這裡卡了我很久的是調用棧保存在哪裡,回想到上一部分畫的圖,其實就在.bss
段後邊.
這裡註意sp
寄存器是棧指針寄存器,fp
寄存器是幀指針寄存器,是不一樣的.
這裡重提一下os/.cargo/config
的內容,"-Cforce-frame-pointers=yes"
代表保存棧指針:
# os/.cargo/config
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
rustflags = [
"-Clink-arg=-Tsrc/linker-qemu.ld", "-Cforce-frame-pointers=yes"
]
此時可以通過fp
的值(保證它不在trap過程中在用戶棧或者內核棧中).
這時候可以創建os/src/stack_trace.rs
:
use core::{arch::asm, ptr};
pub unsafe fn print_stack_trace() -> () {
let mut fp: *const usize;
asm!("mv {}, fp", out(reg) fp);
println!("== Begin stack trace ==");
while fp != ptr::null() {
let saved_ra = *fp.sub(1);
let saved_fp = *fp.sub(2);
println!("0x{:016x}, fp = 0x{:016x}", saved_ra, saved_fp);
fp = saved_fp as *const usize;
}
println!("== End stack trace ==");
}
在main.rs
里把它加入main.rs
作為一個子模塊:
mod stack_trace;
編輯lang_items.rs
,在panic
函數裡加入unsafe { print_stack_trace(); }
.
註意添加上依賴use crate::stack_trace::print_stack_trace;
在AppManager
的函數load_app
的所有APP運行完畢的部分加上panic!("Shutdown machine!");
,代替原來的正常關機.
impl AppManager {
pub fn print_app_info(&self) {
println!("[kernel] num_app = {}", self.num_app);
for i in 0..self.num_app {
println!(
"[kernel] app_{} [{:#x}, {:#x})",
i,
self.app_start[i],
self.app_start[i + 1]
);
}
}
unsafe fn load_app(&self, app_id: usize) {
if app_id >= self.num_app {
println!("All applications completed!");
panic!("Shutdown machine!");
//shutdown(false);
}
println!("[kernel] Loading app_{}", app_id);
// clear app area
core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
let app_src = core::slice::from_raw_parts(
self.app_start[app_id] as *const u8,
self.app_start[app_id + 1] - self.app_start[app_id],
);
let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
app_dst.copy_from_slice(app_src);
// Memory fence about fetching the instruction memory
// It is guaranteed that a subsequent instruction fetch must
// observes all previous writes to the instruction memory.
// Therefore, fence.i must be executed after we have loaded
// the code of the next app into the instruction memory.
// See also: riscv non-priv spec chapter 3, 'Zifencei' extension.
asm!("fence.i");
}
pub fn get_current_app(&self) -> usize {
self.current_app
}
pub fn move_to_next_app(&mut self) {
self.current_app += 1;
}
}
然後直接在os
目錄下make run
.如果報錯了
error: unused import: `crate::sbi::shutdown`
--> src/batch.rs:3:5
|
3 | use crate::sbi::shutdown;
| ^^^^^^^^^^^^^^^^^^^^
|
直接註釋掉use crate::sbi::shutdown;
即可.
這樣就可以得到結果:
[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name : riscv-virtio,qemu
[rustsbi] Platform SMP : 1
[rustsbi] Platform Memory : 0x80000000..0x88000000
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] num_app = 5
[kernel] app_0 [0x8020a038, 0x8020b360)
[kernel] app_1 [0x8020b360, 0x8020c730)
[kernel] app_2 [0x8020c730, 0x8020dcd8)
[kernel] app_3 [0x8020dcd8, 0x8020f090)
[kernel] app_4 [0x8020f090, 0x80210440)
[kernel] Loading app_0
Hello, world!
[kernel] Application exited with code 0
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_2
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
All applications completed!
== Begin stack trace ==
0x0000000080201244, fp = 0x0000000080206cf0
0x0000000080201a78, fp = 0x0000000080206d30
0x0000000080200ed8, fp = 0x0000000080206da0
0x00000000802010bc, fp = 0x0000000080206e00
0x0000000080201724, fp = 0x0000000080206ef0
0x0000000080200a70, fp = 0x0000000080208fc0
0x0000000080400032, fp = 0x0000000080209000
0x0000000000000000, fp = 0x0000000000000000
== End stack trace ==
make: *** [Makefile:64: run-inner] Error 255
後續題目
問答題
1. 函數調用與系統調用有何區別?
- 函數調用用普通的控制流指令,不涉及特權級的切換;系統調用使用專門的指令(如 RISC-V 上的 ecall),會切換到內核特權級。
- 函數調用可以隨意指定調用目標;系統調用只能將控制流切換給調用操作系統內核給定的目標。
這裡註意去看之前提到的trap
處理流程:
print!
->print
->Stdout.write_fmt
->write
->sys_write
->syscall
是在user
層的函數調用鏈.syscall
->sys_write
->print!
->Stdout.write_fmt
->console_putchar
是在os
層的函數調用鏈.- 使用
x10~x17
作為其中的橋梁
2. 為了方便操作系統處理,M態軟體會將 S 態異常/中斷委托給 S 態軟體,請指出有哪些寄存器記錄了委托信息,rustsbi 委托了哪些異常/中斷?(也可以直接給出寄存器的值)
這個問題主要是在rust-sbi
之中,這個題當然可以通過直接問GPT得出答案,但是為了增強我們的查詢能力,假如GPT不知道或者我們需要更多的更準確更標準的信息,我們可以查看RISC-V手冊 (ustc.edu.cn),尋找其中內容.
我們在其中搜索委托,可以看到 10.5 現代操作系統的監管者模式 這一章中提到了關於RISC-V的委托機制的描述.
預設情況下,發生所有異常(不論在什麼許可權模式下)的時候,控制權都會被移交到 M 模式的異常處理程式。但是 Unix 系統中的大多數例外都應該進行 S 模式下的系統調 用。M 模式的異常處理程式可以將異常重新導向 S 模式,但這些額外的操作會減慢大多數 異常的處理速度。因此,RISC-V 提供了一種異常委托機制。通過該機制可以選擇性地將中 斷和同步異常交給 S 模式處理,而完全繞過 M 模式。
這裡提到兩個CSR,mideleg
和medeleg
,分別可以把中斷和同步異常委托給S模式:
mideleg(Machine Interrupt Delegation,機器中斷委托)CSR 控制將哪些中斷委托給 S 模式。
M 模式還可以通過 medeleg CSR 將同步異常委托給 S 模式。
這裡同步異常的名字可能比較奇怪,容易讓人想到還有別的類型的異常,但是實際上異常只有兩類:同步異常,中斷:
RISC-V 將 異常分為兩類。一類是同步異常,這類異常在指令執行期間產生,如訪問了無效的存儲器 地址或執行了具有無效操作碼的指令時。另一類是中斷,它是與指令流非同步的外部事件, 比如滑鼠的單擊。
請註意,無論委派設置是怎樣的,發生異常時控制權都不會移交給許可權更低的模式。 在 M 模式下發生的異常總是在 M 模式下處理。在 S 模式下發生的異常,根據具體的委派 設置,可能由 M 模式或 S 模式處理,但永遠不會由 U 模式處理。
圖中含有關於中斷和同步異常的描述,前半個表格是關於 中斷 的後半個表格是關於 同步異常 的.把對應的位設置為高就可以把對應異常委托給S模式.
例如, mideleg[5]對應於 S 模式的時鐘中斷,如果把它置位,S 模式的時鐘中斷將會移交 S 模式 的異常處理程式,而不是 M 模式的異常處理程式。
例如,置上 medeleg[15]便會把 store page fault(store 過程中出現的缺頁)委托給 S 模式。
使用make debug
進入debug
模式,使用GDB命令break rust_main
打斷點,然後使用c
命令使得程式停在rust_main
之前.
使用指令info register mideleg
和info register medeleg
可以讀取到兩個寄存器的值:
mideleg 0x666 1638
medeleg 0xf0b5ff 15775231
這裡發現和答案對應不上,對應不上就對了,看上圖,實際上mideleg
的有效位數為奇數位,應該讓0x666
按位與 一個b1010 1010 1010
,得到的就是0x222
了.
同理對於medeleg
的0xf0b5ff
應該取其0~15
位缺10
和14
位,應該 按位與 一個b1011 1011 1111 1111
得到0xb1ff
,這裡似乎還算和答案不一致,但是我們可以自圓其說,先不管了.
3. 如果操作系統以應用程式庫的形式存在,應用程式可以通過哪些方式破壞操作系統?
這題主要是對比第一章和第二章操作系統的不同,找到第一章操作系統的問題.
第一印象產生出來主要是我們第二章忙活的部分,也就是在於關於調用更高級別指令的問題.但是調用更高級別的指令,我們如果用操作系統來攔截這些指令,就可以保證我們的操作都是已經定義的,就不會出現問題了.
這題所有的缺點,反過來就是第二章所有的優點.答案已經寫得很完美了.
如果操作系統以應用程式庫的形式存在,那麼編譯器在鏈接OS庫時會把應用程式跟OS庫鏈接成一個可執行文件,兩者處於同一地址空間,這也是LibOS(Unikernel)架構,此時存在如下幾個破壞操作系統的方式:
- 緩衝區溢出:應用程式可以覆蓋寫其合法記憶體邊界之外的部分,這可能會危及 OS;
- 整數溢出:當對整數值的運算產生的值超出整數數據類型可以表示的範圍時,就會發生整數溢出, 這可能會導致OS出現意外行為和安全漏洞。 例如,如果允許應用程式分配大量記憶體,攻擊者可能會在記憶體分配常式中觸發整數溢出,從而可能導致緩衝區溢出或其他安全漏洞;
- 系統調用攔截:應用程式可能會攔截或重定向系統調用,從而可能損害OS的行為。例如,攻擊者可能會攔截讀取敏感文件的系統調用並將其重定向到他們選擇的文件,從而可能危及 unikernel 的安全性。
- 資源耗盡:應用程式可能會消耗記憶體或網路帶寬等資源,可能導致拒絕服務或其他安全漏洞。
4. 編譯器/操作系統/處理器如何合作,可採用哪些方法來保護操作系統不受應用程式的破壞?
仍然是參考官方文檔的參考答案.
硬體操作系統運行在一個硬體保護的安全執行環境中,不受到應用程式的破壞;應用程式運行在另外一個無法破壞操作系統的受限執行環境中。 現代CPU提供了很多硬體機制來保護操作系統免受惡意應用程式的破壞,包括如下幾個:
- 特權級模式:處理器能夠設置不同安全等級的執行環境,即用戶態執行環境和內核態特權級的執行環境。處理器在執行指令前會進行特權級安全檢查,如果在用戶態執行環境中執行內核態特權級指令,會產生異常阻止當前非法指令的執行。
- TEE(可信執行環境):CPU的TEE能夠構建一個可信的執行環境,用於抵禦惡意軟體或攻擊,能夠確保處理敏感數據的應用程式(例如移動銀行和支付應用程式)的安全。
- ASLR(地址空間佈局隨機化):ASLR 是CPU的一種隨機化進程地址空間佈局的安全功能,其能夠隨機生成進程地址空間,例如棧、共用庫等關鍵部分的起始地址,使攻擊者預測特定數據或代碼的位置。
5. RISC-V處理器的S態特權指令有哪些,其大致含義是什麼,有啥作用?
這個問題之前我們說過它的分類,而具體的S態特權指令則應該查詢參考書目RISC-V手冊 (ustc.edu.cn).
用戶態軟體為獲得內核態操作系統的服務功能而執行特殊指令:
1. 指令本身屬於高特權級的指令,如 sret
指令(表示從 S 模式返回到 U 模式)
2. 指令訪問了 S模式特權級下才能訪問的寄存器 或記憶體,如表示S模式系統狀態的 控制狀態寄存器 sstatus
等
通過查閱手冊我們可以得出特權指令分為如下圖所示:
sret的作用:
mret的作用:
wfi的作用:
sfence.vma的作用:
訪問CSR的指令,這裡直接列出M模式下CSR的列表,再S模式下的CSR命名則是把名字里的m
,更換為s
:
另外還有一些CSR專用的指令:
圖中的CSR相關指令為:
6. RISC-V處理器在用戶態執行特權指令後的硬體層面的處理過程是什麼?
包括上部分畫的圖從來都是講的怎麼做特權級切換,但是我們總是忽略在切換上下文的時候的CSR的變化,這些部分想想就是通過硬體來改變的,這個問題剛好對我們是一個非常合適的提醒.
CPU 執行完一條指令(如 ecall )並準備從用戶特權級 陷入( Trap )到 S 特權級的時候,硬體會自動完成如下這些事情:
- sstatus 的 SPP 欄位會被修改為 CPU 當前的特權級(U/S)。
- sepc 會被修改為 Trap 處理完成後預設會執行的下一條指令的地址。
- scause/stval 分別會被修改成這次 Trap 的原因以及相關的附加信息。
- cpu 會跳轉到 stvec 所設置的 Trap 處理入口地址,並將當前特權級設置為 S ,然後從Trap 處理入口地址處開始執行
CPU 完成 Trap 處理準備返回的時候,需要通過一條 S 特權級的特權指令 sret 來完成,這一條指令具體完成以下功能: * CPU 會將當前的特權級按照 sstatus 的 SPP 欄位設置為 U 或者 S ; * CPU 會跳轉到 sepc 寄存器指向的那條指令,然後繼續執行。
7. 操作系統在完成用戶態<–>內核態雙向切換中的一般處理過程是什麼?
這個更詳細的要看關於上一部分的綠色部分,關於ecall
->x10~x17
->trap_handler
->sret
這一部分的描述,可以看看(019 在main中測試本章實現)里的大圖.
當 CPU 在用戶態特權級( RISC-V 的 U 模式)運行應用程式,執行到 Trap,切換到內核態特權級( RISC-V的S 模式),批處理操作系統的對應代碼響應 Trap,並執行系統調用服務,處理完畢後,從內核態返回到用戶態應用程式繼續執行後續指令。
8. 程式陷入內核的原因有中斷、異常和陷入(系統調用),請問 riscv64 支持哪些中斷 / 異常?如何判斷進入內核是由於中斷還是異常?描述陷入內核時的幾個重要寄存器及其值。
這裡就還是要看這張圖了
要判斷當前的中斷和異常情況只需要看scause
,RISC-V手冊 (ustc.edu.cn) 對它的描述是:
scause 按圖 10.3 根據異常類型設置,stval 被設置成出錯的地址或者其它特定異常的信息字。
Trap
時重要的寄存器也還是看這張圖,這裡直接列出M模式下CSR的列表,再S模式下的CSR命名則是把名字里的m
,更換為s
:
9. 在哪些情況下會出現特權級切換:用戶態–>內核態,以及內核態–>用戶態?
用戶態到內核態的切換已經重覆了很多次了:
上層軟體執行過程中出現了一些異常或 特殊情況 , 需要用到執行環境中提供的功能
1. 這裡可以看到雖然都叫做 異常 但是實際上有一部分情況是特殊情況需要使用執行環境中的功能,不能非黑即白地把 異常 理解為 壞的
2. 用戶態應用直接觸發從用戶態到內核態的異常的原因總體上可以分為兩種
1. 其一是用戶態軟體為獲得內核態操作系統的服務功能而執行特殊指令
1. 指令本身屬於高特權級的指令,如 sret
指令(表示從 S 模式返回到 U 模式)
2. 指令訪問了 S模式特權級下才能訪問的寄存器 或記憶體,如表示S模式系統狀態的 控制狀態寄存器 sstatus
等
2. 其二是在執行某條指令期間產生了錯誤(如執行了用戶態不允許執行的指令或者其他錯誤)並被 CPU 檢測到
但是從內核態到用戶態,我們馬上能想到的只有sret
,但是想想我們這一章實現的操作系統是什麼時候開始運行APP的呢,運行的時候是怎麼進入用戶態的呢,更具體更詳細的似乎沒有?
想想上一部分我們在官方手冊中可以看到:
唯一一種能夠使得 CPU 特權級下降的方法就是執行 Trap 返回的特權指令,如 sret
、mret
等
這時候我們再去RISC-V手冊 (ustc.edu.cn) 尋找關於Trap
返回的特權指令:
監管者異常返回指令 sret 與 mret 的行為相同,但它作用於 S 模式的異常處理 CSR,而不 是 M 模式的 CSR
可以看到Trap
的返回指令只有sret
和mret
.
這裡再重覆一下它們倆的作用.
sret的作用:
mret的作用:
10. Trap上下文的含義是啥?在本章的操作系統中,Trap上下文的具體內容是啥?如果不進行Trap上下文的保存於恢復,會出現什麼情況?
這個直接聯繫到我們對於TrapContext
的定義代碼:
pub struct TrapContext {
/// general regs[0..31]
pub x: [usize; 32],
/// CSR sstatus
pub sstatus: Sstatus,
/// CSR sepc
pub sepc: usize,
}
這裡我們可以反過來回想到,Trap
的上下文就是CSR保存的關於Trap
的信息,以及通用寄存器.
如果不能進行Trap
的上下文保存與恢復,CPU就不能正確恢復到原來的特權級.
實驗練習
實踐作業
sys_write 安全檢查
ch2 中,我們實現了第一個系統調用 sys_write
,這使得我們可以在用戶態輸出信息。但是 os 在提供服務的同時,還有保護 os 本身以及其他用戶程式不受錯誤或者惡意程式破壞的功能。
由於還沒有實現虛擬記憶體,我們可以在用戶程式中指定一個屬於其他程式字元串,並將它輸出,這顯然是不合理的,因此我們要對 sys_write 做檢查:
- sys_write 僅能輸出位於程式本身記憶體空間內的數據,否則報錯。
為AppManager
創建一個獲取當前APP地址的方法,並且因為每次載入APP之後current_app
號加一,所以需要讀取的範圍是self.app_start[self.current_app]~self.app_start[self.current_app-1]
,計算出APP大小作為偏移量,然後通過基指針計算即可:
impl AppManager {
pub fn print_app_info(&self) {
println!("[kernel] num_app = {}", self.num_app);
for i in 0..self.num_app {
println!(
"[kernel] app_{} [{:#x}, {:#x})",
i,
self.app_start[i],
self.app_start[i + 1]
);
}
}
unsafe fn load_app(&self, app_id: usize) {
if app_id >= self.num_app {
println!("All applications completed!");
//panic!("Shutdown machine!");
shutdown(false);
}
println!("[kernel] Loading app_{}", app_id);
// clear app area
core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
let app_src = core::slice::from_raw_parts(
self.app_start[app_id] as *const u8,
self.app_start[app_id + 1] - self.app_start[app_id],
);
let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
app_dst.copy_from_slice(app_src);
// Memory fence about fetching the instruction memory
// It is guaranteed that a subsequent instruction fetch must
// observes all previous writes to the instruction memory.
// Therefore, fence.i must be executed after we have loaded
// the code of the next app into the instruction memory.
// See also: riscv non-priv spec chapter 3, 'Zifencei' extension.
asm!("fence.i");
}
pub fn get_current_app(&self) -> usize {
self.current_app
}
pub fn move_to_next_app(&mut self) {
self.current_app += 1;
}
pub fn get_current_app_range(&self) -> (usize, usize) {
(APP_BASE_ADDRESS,APP_BASE_ADDRESS+self.app_start[self.current_app]-self.app_start[self.current_app-1])
}
}
pub fn get_current_app_range() -> (usize, usize) {
APP_MANAGER.exclusive_access().get_current_app_range()
}
在batch.rs
中創建一個方法,計算用戶棧的範圍:
pub fn get_user_stack_range() -> (usize, usize) {
(USER_STACK.get_sp() - USER_STACK_SIZE, USER_STACK.get_sp())
}
改造sys_wirte
,如果變數不是在上述兩個範圍內,那麼就sys_exit()
:
/// write buf of length `len` to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
let app_range = get_current_app_range();
let stack_range = get_user_stack_range();
//println!("range: [{:#x}, {:#x})\n", range.0,range.1);
let buf_pointer = buf as usize;
//println!("buf_pointer: {:#x}\n", buf_pointer);
if (buf_pointer < app_range.0 || buf_pointer >= app_range.1) && (buf_pointer < stack_range.0 || buf_pointer >= stack_range.1) {
sys_exit(fd as i32)
}
match fd {
FD_STDOUT => {
let slice = unsafe { core::slice::from_raw_parts(buf, len) };
let str = core::str::from_utf8(slice).unwrap();
print!("{}", str);
len as isize
}
_ => {
panic!("Unsupported fd in sys_write!");
}
}
}
這裡其實在編寫過程中有一個小的踩坑的過程的(一直踩坑一直G),
最開始只想到了參數的值必須在被載入的範圍內,也就是上述app_range
的範圍內,然後發現,APP2老報錯,我把sys_exit
換成println!
,發現每次 訪問變數 都會出問題,這時候也問了同義千問sp
的作用,其中有一句讓我豁然開朗:
棧指針寄存器用於跟蹤當前棧頂的位置。每當函數調用發生時,參數和局部變數會被壓入棧中,棧指針會相應地下移(在RISC-V中,棧向下增長)。
會想起牢丘的51單片機實驗,他老說保存現場保存現場,其實保存的就是局部變數,局部變數就是存在通用寄存器里嘛!(也不知道我有沒有融會貫通對)
這時候參考了別人的代碼,就又加上了這部分.
測試的時候需要把自己的修改放到官方的代碼:~/App/rCore-Tutorial-v3
里去,git checkout ch2-lab
,運行make run TEST=1
.
這裡運行的時候報錯了:
(rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add riscv64gc-unknown-none-elf
riscv64gc-unknown-none-elf (installed)
cargo install cargo-binutils --vers =0.3.3
Updating `ustc` index
Installing cargo-binutils v0.3.3
error: failed to compile `cargo-binutils v0.3.3`, intermediate artifacts can be found at `/tmp/cargo-installvTJxCF`
Caused by:
package `regex-automata v0.4.7` cannot be built because it requires rustc 1.65 or newer, while the currently active rustc version is 1.64.0-nightly
make: *** [Makefile:45: env] Error 101
說是rustc
的等級太低,重新更新一下rustc
,然後安裝依賴:
rustup update
發現仍然無效.
但是這裡踩了一個新坑,更新rust
之後要看最新版本需要 重啟命令行.
仍然無效的原因是:
rustup show
Default host: x86_64-unknown-linux-gnu
rustup home: /home/winddevil/.rustup
installed toolchains
--------------------
stable-x86_64-unknown-linux-gnu
nightly-2022-07-20-x86_64-unknown-linux-gnu
nightly-2024-05-01-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
installed targets for active toolchain
--------------------------------------
riscv64gc-unknown-none-elf
x86_64-unknown-linux-gnu
active toolchain
----------------
nightly-2022-07-20-x86_64-unknown-linux-gnu (overridden by '/home/winddevil/App/rCore-Tutorial-v3/rust-toolchain.toml')
rustc 1.64.0-nightly (9a7b7d5e5 2022-07-19)
可以看到rust-toolchain.toml
規定的channel
影響了我們的版本問題.
進去把它修改為最新的channel = "nightly-2024-07-30"
就可以了.
但是這樣又產生新的衝突:
warning: the feature `panic_info_message` has been stable since 1.82.0-nightly and no longer requires an attribute to enable
--> src/lib.rs:3:12
|
3 | #![feature(panic_info_message)]
| ^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(stable_features)]` on by default
error[E0599]: no method named `unwrap` found for struct `PanicMessage` in the current scope
--> src/lang_items.rs:5:36
|
5 | let err = panic_info.message().unwrap();
| ^^^^^^ method not found in `PanicMessage<'_>`
For more information about this error, try `rustc --explain E0599`.
warning: `user_lib` (lib) generated 1 warning
error: could not compile `user_lib` (lib) due to 1 previous error; 1 warning emitted
make[1]: *** [Makefile:20: binary] Error 101
make[1]: Leaving directory '/home/winddevil/App/rCore-Tutorial-v3/user'
make: *** [Makefile:53: kernel] Error 2
註釋掉App/rCore-Tutorial-v3/user/src/lib.rs
的#![feature(panic_info_message)]
.
把所有的panic_info.message().unwarp()
改成panic_info.message().as_str().unwrap_or("no message")
就行了.
不修改的時候的輸出:
[rustsbi] RustSBI version 0.2.0-alpha.6
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: ssoft, stimer, sext (0x222)
[rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
[rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
[rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
[rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
[rustsbi] enter supervisor 0x80200000
[kernel] Hello, world!
[kernel] num_app = 2
[kernel] app_0 [0x80209020, 0x8020c768)
[kernel] app_1 [0x8020c768, 0x8020ffa0)
[kernel] Loading app_0
[kernel] Panicked at src/trap/mod.rs:45 no message
可以看到載入app0的時候就出現了panic
.
修改之後:
[rustsbi] RustSBI version 0.2.0-alpha.6
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: ssoft, stimer, sext (0x222)
[rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
[rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
[rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
[rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
[rustsbi] enter supervisor 0x80200000
[kernel] Hello, world!
[kernel] num_app = 2
[kernel] app_0 [0x80209020, 0x8020c768)
[kernel] app_1 [0x8020c768, 0x8020ffa0)
[kernel] Loading app_0
[kernel] Application exited with code 1
[kernel] Loading app_1
[kernel] Panicked at src/syscall/fs.rs:27 Unsupported fd in sys_write!
可以看到app_0
的運行過程中觸發了Application exited with code 1
,沒按照預期運行.而app_1
則是更加重量級地跳了.
因為我們可以面向結果編程,所以我打算自己看一看app_0
的測例是什麼貴物;
#![no_std]
#![no_main]
use core::arch::asm;
#[macro_use]
extern crate user_lib;
extern crate core;
use core::slice;
use user_lib::{write, STDOUT};
/// 正確輸出:
/// Test write0 OK!
const STACK_SIZE: usize = 0x1000;
unsafe fn r_sp() -> usize {
let mut sp: usize;
asm!("mv {}, sp", out(reg) sp);
sp
}
unsafe fn stack_range() -> (usize, usize) {
let sp = r_sp();
let top = (sp + STACK_SIZE - 1) & (!(STACK_SIZE - 1));
(top - STACK_SIZE, top)
}
#[no_mangle]
pub fn main() -> i32 {
assert_eq!(
write(STDOUT, unsafe {
#[allow(clippy::zero_ptr)]
slice::from_raw_parts(0x0 as *const _, 10)
}),
-1
);
let (bottom, top) = unsafe { stack_range() };
assert_eq!(
write(STDOUT, unsafe {
slice::from_raw_parts((top - 5) as *const _, 10)
}),
-1
);
assert_eq!(
write(STDOUT, unsafe {
slice::from_raw_parts((bottom - 5) as *const _, 10)
}),
-1
);
// TODO: test string located in .data section
println!("Test write0 OK!");
0
}
第一點,我們可以看到實際上它希望我們每次嘗試訪問不在許可權內的地址後不是退出app而是選擇阻止,並且繼續運行下麵的部分.
因此可以把sys_write
更改為:
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
let app_range = get_current_app_range();
let stack_range = get_user_stack_range();
//println!("range: [{:#x}, {:#x})\n", range.0,range.1);
let buf_pointer = buf as usize;
//println!("buf_pointer: {:#x}\n", buf_pointer);
if (buf_pointer < app_range.0 || buf_pointer >= app_range.1) && (buf_pointer < stack_range.0 || buf_pointer >= stack_range.1) {
println!("Out of range!");
return (len as isize)
//sys_exit(fd as i32)
}
match fd {
FD_STDOUT => {
let slice = unsafe { core::slice::from_raw_parts(buf, len) };
let str = core::str::from_utf8(slice).unwrap();
print!("{}", str);
len as isize
}
_ => {
panic!("Unsupported fd in sys_write!");
}
此時輸出為:
[rustsbi] RustSBI version 0.2.0-alpha.6
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation: RustSBI-QEMU Version 0.0.2
[rustsbi-dtb] Hart count: cluster0 with 1 cores
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: ssoft, stimer, sext (0x222)
[rustsbi] medeleg: ima, ia, bkpt, la, sa, uecall, ipage, lpage, spage (0xb1ab)
[rustsbi] pmp0: 0x10000000 ..= 0x10001fff (rwx)
[rustsbi] pmp1: 0x80000000 ..= 0x8fffffff (rwx)
[rustsbi] pmp2: 0x0 ..= 0xffffffffffffff (---)
[rustsbi] enter supervisor 0x80200000
[kernel] Hello, world!
[kernel] num_app = 2
[kernel] app_0 [0x80209020, 0x8020c768)
[kernel] app_1 [0x8020c768, 0x8020ffa0)
[kernel] Loading app_0
Out of range!
Out of range!
Out of range!
[kernel] Application exited with code -1
[kernel] Loading app_1
[kernel] Panicked at src/syscall/fs.rs:29 Unsupported fd in sys_write!
可以看到app_0
觸發了三次Out of range!
,但是最後還是以-1
為結束.
這時候懷疑這個-1
是用戶態的println!
不能正常運行導致的.
嘗試運行make run
而不啟用TEST
.這樣根據user/Makefile
文件的這處描述,就會運行普通的app :
TEST ?= 0
ifeq ($(TEST), 0)
APPS := $(filter-out $(wildcard $(APP_DIR)/test*.rs), $(wildcard $(APP_DIR)/*.rs))
else
APPS := $(wildcard $(APP_DIR)/test$(TEST)*.rs)
endif
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
看一下hello_world
能不能正常輸出.
發現如果只留下hello_world.rs
一個文件還是不能正常輸出.
發現ch2-lab
的console.rs
的實現方式和我們之前的不一樣.但是難道這樣編譯出來的指針就不在用戶棧或者app載入的地方了嗎?這裡存疑.
嘗試把測例移植到我們自己的代碼裡邊.把test1_write0.rs
和test1_write1.rs
拷貝到user/src/bin
下麵,在user
下運行make build
.
運行之後發現test1_write0
里第一個錯誤報了out of range!
但是第二個沒導致斷言assert_eq!
卡住程式,這時候具體看兩次報錯是什麼情況,可以發現第一個嘗試是在0x0
寫入,第二個是在距離棧頂大小為5
的位置寫入大小為10
的數據,這提醒我們增加一個對於幀尾的判斷:
/// write buf of length `len` to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
let app_range = get_current_app_range();
let stack_range = get_user_stack_range();
// println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
// println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
let buf_begin_pointer = buf as usize;
let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
// println!("buf_pointer: {:#x}", buf_pointer);
if ((buf_begin_pointer < app_range.0 || buf_begin_pointer >= app_range.1) &&
(buf_begin_pointer < stack_range.0 || buf_begin_pointer >= stack_range.1))||
((buf_end_pointer < app_range.0 || buf_end_pointer >= app_range.1) &&
(buf_end_pointer < stack_range.0 || buf_end_pointer >= stack_range.1))
{
println!("out of range!");
return -1 as isize;
// sys_exit(fd as i32)
}
match fd {
FD_STDOUT => {
let slice = unsafe { core::slice::from_raw_parts(buf, len) };
let str = core::str::from_utf8(slice).unwrap();
print!("{}", str);
len as isize
}
_ => {
panic!("Unsupported fd in sys_write!");
}
}
}
這時候再次運行,發現第三次嘗試報錯,原因是它試圖在距離棧底位置距離為5
的地方寫入10
的數據,這下不管是幀頭還算幀尾都在範圍內了,那麼為什麼會是一個測試例子呢( 預設是錯誤的例子 ),因為距離棧底為5
的位置不在棧里,而後邊5
個單位在棧里,這樣就導致了對於全局數據位置和臨時數據位置的跨越寫入,這時候把代碼改成:
/// write buf of length `len` to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
let app_range = get_current_app_range();
let stack_range = get_user_stack_range();
// println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
// println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
let buf_begin_pointer = buf as usize;
let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
// println!("buf_begin_pointer: {:#x}", buf_begin_pointer);
// println!("buf_end_pointer: {:#x}", buf_end_pointer);
if !(
(buf_begin_pointer >= app_range.0 && buf_begin_pointer < app_range.1) &&
(buf_end_pointer >= app_range.0 && buf_end_pointer < app_range.1)
)&&
!(
(buf_begin_pointer >= stack_range.0 && buf_begin_pointer < stack_range.1) &&
(buf_end_pointer >= stack_range.0 && buf_end_pointer < stack_range.1)
)
{
println!("out of range!");
return -1 as isize;
// sys_exit(fd as i32)
}
match fd {
FD_STDOUT => {
let slice = unsafe { core::slice::from_raw_parts(buf, len) };
let str = core::str::from_utf8(slice).unwrap();
print!("{}", str);
len as isize
}
_ => {
panic!("Unsupported fd in sys_write!");
}
}
}
這時候的運行結果是:
[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name : riscv-virtio,qemu
[rustsbi] Platform SMP : 1
[rustsbi] Platform Memory : 0x80000000..0x88000000
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] num_app = 7
[kernel] app_0 [0x8020b048, 0x8020c370)
[kernel] app_1 [0x8020c370, 0x8020d740)
[kernel] app_2 [0x8020d740, 0x8020ece8)
[kernel] app_3 [0x8020ece8, 0x802100a0)
[kernel] app_4 [0x802100a0, 0x80211450)
[kernel] app_5 [0x80211450, 0x80212d80)
[kernel] app_6 [0x80212d80, 0x802147c0)
[kernel] Loading app_0
Hello, world!
[kernel] Application exited with code 0
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_2
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_5
out of range!
out of range!
out of range!
Test write0 OK!
[kernel] Application exited with code 0
[kernel] Loading app_6
== Begin stack trace ==
0x000000008020149a, fp = 0x0000000080206d40
0x0000000080201c02, fp = 0x0000000080206d80
0x000000008020073c, fp = 0x0000000080206e00
0x0000000080201790, fp = 0x0000000080206ef0
0x0000000080200d8c, fp = 0x0000000080209fc0
0x0000000080400032, fp = 0x000000008020a000
0x0000000000000000, fp = 0x0000000000000000
== End stack trace ==
make: *** [Makefile:64: run-inner] Error 255
第一個測試用例通過了,但是第二個還沒有.果然如此,又要繼續踩坑.
可以看到是載入之後就出現了BUG,而且報了調用棧,說明是觸發了panic!
.
查看代碼,原來是進入了sys_write
的default
的分支,我們試著不報panic
,把這句註釋掉:
/// write buf of length `len` to a file with `fd`
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
let app_range = get_current_app_range();
let stack_range = get_user_stack_range();
// println!("app_range: [{:#x}, {:#x})", app_range.0,app_range.1);
// println!("stack_range: [{:#x}, {:#x})", stack_range.0,stack_range.1);
let buf_begin_pointer = buf as usize;
let buf_end_pointer = unsafe{buf.offset(len as isize)} as usize;
// println!("buf_pointer: {:#x}", buf_pointer);
if !(
(buf_begin_pointer >= app_range.0 && buf_begin_pointer < app_range.1) &&
(buf_end_pointer >= app_range.0 && buf_end_pointer < app_range.1)
)||
(
(buf_begin_pointer >= stack_range.0 && buf_begin_pointer < stack_range.1) &&
(buf_end_pointer >= stack_range.0 && buf_end_pointer < stack_range.1)
)
{
println!("out of range!");
return -1 as isize;
// sys_exit(fd as i32)
}
match fd {
FD_STDOUT => {
let slice = unsafe { core::slice::from_raw_parts(buf, len) };
let str = core::str::from_utf8(slice).unwrap();
print!("{}", str);
len as isize
}
_ => {
-1 as isize
//panic!("Unsupported fd in sys_write!");
}
}
}
OK,就此解決了:
[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name : riscv-virtio,qemu
[rustsbi] Platform SMP : 1
[rustsbi] Platform Memory : 0x80000000..0x88000000
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] num_app = 7
[kernel] app_0 [0x8020b048, 0x8020c370)
[kernel] app_1 [0x8020c370, 0x8020d740)
[kernel] app_2 [0x8020d740, 0x8020ece8)
[kernel] app_3 [0x8020ece8, 0x802100a0)
[kernel] app_4 [0x802100a0, 0x80211450)
[kernel] app_5 [0x80211450, 0x80212d80)
[kernel] app_6 [0x80212d80, 0x802147c0)
[kernel] Loading app_0
Hello, world!
[kernel] Application exited with code 0
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_2
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_5
out of range!
out of range!
out of range!
Test write0 OK!
[kernel] Application exited with code 0
[kernel] Loading app_6
string from data section
strinstring from stack section
strin
Test write1 OK!
[kernel] Application exited with code 0
All applications completed!
這裡不要因為解決了就忘記解讀第二個測例test1_write1.rs
,查看它的源碼,發現它分別測試的是:
- 能否跳過未支持的
fd
- 能否訪問
- 存在
data
段的靜態變數 - 存在
stack
段的局部變數 - 存在
stack
段的局部變數的部分指針
- 存在
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use user_lib::write;
const DATA_STRING: &str = "string from data section\n";
const STDOUT: usize = 1;
/// 正確輸出:
/// string from data section
/// strinstring from stack section
/// strin
/// Test write1 OK!
#[no_mangle]
pub fn main() -> i32 {
assert_eq!(write(1234, DATA_STRING.as_bytes()), -1);
assert_eq!(
write(STDOUT, DATA_STRING.as_bytes()),
DATA_STRING.len() as isize
);
assert_eq!(write(STDOUT, &DATA_STRING.as_bytes()[..5]), 5);
let stack_string = "string from stack section\n";
assert_eq!(
write(STDOUT, stack_string.as_bytes()),
stack_string.len() as isize
);
assert_eq!(write(STDOUT, &stack_string.as_bytes()[..5]), 5);
println!("\nTest write1 OK!");
0
}
問答作業
1. 正確進入 U 態後,程式的特征還應有:使用 S 態特權指令,訪問 S 態寄存器後會報錯。請自行測試這些內容 (運行 Rust 三個 bad 測例 ) ,描述程式出錯行為,註明你使用的 sbi 及其版本。
這裡可以直接看我們在上一部分的運行結果:
[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name : riscv-virtio,qemu
[rustsbi] Platform SMP : 1
[rustsbi] Platform Memory : 0x80000000..0x88000000
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] num_app = 5
[kernel] app_0 [0x8020a038, 0x8020b360)
[kernel] app_1 [0x8020b360, 0x8020c730)
[kernel] app_2 [0x8020c730, 0x8020dcd8)
[kernel] app_3 [0x8020dcd8, 0x8020f090)
[kernel] app_4 [0x8020f090, 0x80210440)
[kernel] Loading app_0
Hello, world!
[kernel] Application exited with code 0
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
[kernel] Loading app_2
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
All applications completed!
出錯行為由trap_handler
分類和處理:
#[no_mangle]
/// handle an interrupt, exception, or system call from user space
pub fn trap_handler(cx: &mut TrapContext) -> &mut TrapContext {
let scause = scause::read(); // get trap cause
let stval = stval::read(); // get extra value
match scause.cause() {
Trap::Exception(Exception::UserEnvCall) => {
cx.sepc += 4;
cx.x[10] = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]) as usize;
}
Trap::Exception(Exception::StoreFault) | Trap::Exception(Exception::StorePageFault) => {
println!("[kernel] PageFault in application, kernel killed it.");
run_next_app();
}
Trap::Exception(Exception::IllegalInstruction) => {
println!("[kernel] IllegalInstruction in application, kernel killed it.");
run_next_app();
}
_ => {
panic!(
"Unsupported trap {:?}, stval = {:#x}!",
scause.cause(),
stval
);
}
}
cx
}
把其中報錯的部分挑出來.
app_1:
[kernel] Loading app_1
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
其源碼:
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
#[no_mangle]
fn main() -> i32 {
println!("Into Test store_fault, we will insert an invalid store operation...");
println!("Kernel should kill this application!");
unsafe {
core::ptr::null_mut::<u8>().write_volatile(0);
}
0
}
可以看到它是嘗試寫入了一個空指針.這時候就會觸發trap_handler
里的Trap::Exception(Exception::StoreFault)
,意為訪問了無效的記憶體地址.
app_3:
[kernel] Loading app_3
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
其源碼:
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use core::arch::asm;
#[no_mangle]
fn main() -> i32 {
println!("Try to execute privileged instruction in U Mode");
println!("Kernel should kill this application!");
unsafe {
asm!("sret");
}
0
}
可見它是嘗試調用sret
,這是需要S特權級的.因此觸發trap_handler
的Trap::Exception(Exception::IllegalInstruction)
,意為使用了非法指令.
app_4:
[kernel] Loading app_4
Try to access privileged CSR in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
其源碼:
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
use riscv::register::sstatus::{self, SPP};
#[no_mangle]
fn main() -> i32 {
println!("Try to access privileged CSR in U Mode");
println!("Kernel should kill this application!");
unsafe {
sstatus::set_spp(SPP::User);
}
0
}
可見它嘗試寫入sstatus
,也即嘗試訪問S
的CSR
,因此觸發trap_handler
的Trap::Exception(Exception::IllegalInstruction)
,意為使用了非法指令,但是這裡不同的是用指令訪問了S
特權級才能訪問的寄存器.
SBI的版本在os/Cargo.toml
可以看到:
sbi-rt = { version = "0.0.2", features = ["legacy"] }
2. 請結合用例理解 trap.S 中兩個函數 __alltraps
和 __restore
的作用,並回答如下幾個問題:
這裡是trap.S
的內容:
.altmacro
.macro SAVE_GP n
sd x\n, \n*8(sp)
.endm
.macro LOAD_GP n
ld x\n, \n*8(sp)
.endm
.section .text
.globl __alltraps
.globl __restore
.align 2
__alltraps:
csrrw sp, sscratch, sp
# now sp->kernel stack, sscratch->user stack
# allocate a TrapContext on kernel stack
addi sp, sp, -34*8
# save general-purpose registers
sd x1, 1*8(sp)
# skip sp(x2), we will save it later
sd x3, 3*8(sp)
# skip tp(x4), application does not use it
# save x5~x31
.set n, 5
.rept 27
SAVE_GP %n
.set n, n+1
.endr
# we can use t0/t1/t2 freely, because they were saved on kernel stack
csrr t0, sstatus
csrr t1, sepc
sd t0, 32*8(sp)
sd t1, 33*8(sp)
# read user stack from sscratch and save it on the kernel stack
csrr t2, sscratch
sd t2, 2*8(sp)
# set input argument of trap_handler(cx: &mut TrapContext)
mv a0, sp
call trap_handler
__restore:
# case1: start running app by __restore
# case2: back to U after handling trap
mv sp, a0
# now sp->kernel stack(after allocated), sscratch->user stack
# restore sstatus/sepc
ld t0, 32*8(sp)
ld t1, 33*8(sp)
ld t2, 2*8(sp)
csrw sstatus, t0
csrw sepc, t1
csrw sscratch, t2
# restore general-purpuse registers except sp/tp
ld x1, 1*8(sp)
ld x3, 3*8(sp)
.set n, 5
.rept 27
LOAD_GP %n
.set n, n+1
.endr
# release TrapContext on kernel stack
addi sp, sp, 34*8
# now sp->kernel stack, sscratch->user stack
csrrw sp, sscratch, sp
sret
1. L40:剛進入 __restore
時,a0
代表了什麼值。請指出 __restore
的兩種使用情景。
剛剛進入__restore
的時候是a0
是我們傳入__restore
的參數,我們在run_next_app
中調用了這個函數:
/// run next app
pub fn run_next_app() -> ! {
let mut app_manager = APP_MANAGER.exclusive_access();
let current_app = app_manager.get_current_app();
unsafe {
app_manager.load_app(current_app);
}
app_manager.move_to_next_app();
drop(app_manager);
// before this we have to drop local variables related to resources manually
// and release the resources
extern "C" {
fn __restore(cx_addr: usize);
}
unsafe {
__restore(KERNEL_STACK.push_context(TrapContext::app_init_context(
APP_BASE_ADDRESS,
USER_STACK.get_sp(),
)) as *const _ as usize);
}
panic!("Unreachable in batch::run_current_app!");
}
可以看到傳入的是我們主動製造的TrapContext
的指針,TrapContext
內容是APP載入位置APP_BASE_ADDRESS
和用戶棧的指針.
它被調用的情景分為兩種:
- 一個APP運行結束或者出錯之後的APP切換
- 在內核工作之後開始APP的載入和運行
2. L46-L51:這幾行彙編代碼特殊處理了哪些寄存器?這些寄存器的的值對於進入用戶態有何意義?請分別解釋。
代碼在此:
# restore sstatus/sepc
ld t0, 32*8(sp)
ld t1, 33*8(sp)
ld t2, 2*8(sp)
csrw sstatus, t0
csrw sepc,