[rCore學習筆記 020]第二章作業

来源:https://www.cnblogs.com/chenhan-winddevil/p/18336793
-Advertisement-
Play Games

寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請及時提出。 可以聯繫:[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處理流程:

  1. print! -> print -> Stdout.write_fmt -> write -> sys_write -> syscall 是在user層的函數調用鏈.
  2. syscall -> sys_write -> print! -> Stdout.write_fmt -> console_putchar 是在os層的函數調用鏈.
  3. 使用x10~x17作為其中的橋梁

2. 為了方便操作系統處理,M態軟體會將 S 態異常/中斷委托給 S 態軟體,請指出有哪些寄存器記錄了委托信息,rustsbi 委托了哪些異常/中斷?(也可以直接給出寄存器的值)

這個問題主要是在rust-sbi之中,這個題當然可以通過直接問GPT得出答案,但是為了增強我們的查詢能力,假如GPT不知道或者我們需要更多的更準確更標準的信息,我們可以查看RISC-V手冊 (ustc.edu.cn),尋找其中內容.

我們在其中搜索委托,可以看到 10.5 現代操作系統的監管者模式 這一章中提到了關於RISC-V的委托機制的描述.
預設情況下,發生所有異常(不論在什麼許可權模式下)的時候,控制權都會被移交到 M 模式的異常處理程式。但是 Unix 系統中的大多數例外都應該進行 S 模式下的系統調 用。M 模式的異常處理程式可以將異常重新導向 S 模式,但這些額外的操作會減慢大多數 異常的處理速度。因此,RISC-V 提供了一種異常委托機制。通過該機制可以選擇性地將中 斷和同步異常交給 S 模式處理,而完全繞過 M 模式。

這裡提到兩個CSR,midelegmedeleg,分別可以把中斷和同步異常委托給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 mideleginfo register medeleg可以讀取到兩個寄存器的值:

mideleg        0x666    1638
medeleg        0xf0b5ff 15775231 

這裡發現和答案對應不上,對應不上就對了,看上圖,實際上mideleg的有效位數為奇數位,應該讓0x666 按位與 一個b1010 1010 1010,得到的就是0x222了.

同理對於medeleg0xf0b5ff應該取其0~15位缺1014位,應該 按位與 一個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的返回指令只有sretmret.

這裡再重覆一下它們倆的作用.

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-labconsole.rs的實現方式和我們之前的不一樣.但是難道這樣編譯出來的指針就不在用戶棧或者app載入的地方了嗎?這裡存疑.

嘗試把測例移植到我們自己的代碼裡邊.把test1_write0.rstest1_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_writedefault的分支,我們試著不報panic,把這句註釋掉:

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

OK,就此解決了:

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

這裡不要因為解決了就忘記解讀第二個測例test1_write1.rs,查看它的源碼,發現它分別測試的是:

  1. 能否跳過未支持的fd
  2. 能否訪問
    1. 存在data段的靜態變數
    2. 存在stack段的局部變數
    3. 存在stack段的局部變數的部分指針
#![no_std]
#![no_main]

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

const STDOUT: usize = 1;

/// 正確輸出:
/// string from data section
/// strinstring from stack section
/// strin
/// Test write1 OK!

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

問答作業

1. 正確進入 U 態後,程式的特征還應有:使用 S 態特權指令,訪問 S 態寄存器後會報錯。請自行測試這些內容 (運行 Rust 三個 bad 測例 ) ,描述程式出錯行為,註明你使用的 sbi 及其版本。

這裡可以直接看我們在上一部分的運行結果:

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

出錯行為由trap_handler分類和處理:

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

把其中報錯的部分挑出來.

app_1:

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

其源碼:

#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;

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

可以看到它是嘗試寫入了一個空指針.這時候就會觸發trap_handler里的Trap::Exception(Exception::StoreFault),意為訪問了無效的記憶體地址.

app_3:

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

其源碼:

#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;

use core::arch::asm;

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

可見它是嘗試調用sret,這是需要S特權級的.因此觸發trap_handlerTrap::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,也即嘗試訪問SCSR,因此觸發trap_handlerTrap::Exception(Exception::IllegalInstruction),意為使用了非法指令,但是這裡不同的是用指令訪問了S特權級才能訪問的寄存器.

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

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

2. 請結合用例理解 trap.S 中兩個函數 __alltraps 和 __restore 的作用,並回答如下幾個問題:

這裡是trap.S的內容:

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

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

1. L40:剛進入 __restore 時,a0 代表了什麼值。請指出 __restore 的兩種使用情景。

剛剛進入__restore的時候是a0是我們傳入__restore的參數,我們在run_next_app中調用了這個函數:

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

可以看到傳入的是我們主動製造的TrapContext的指針,TrapContext內容是APP載入位置APP_BASE_ADDRESS和用戶棧的指針.

它被調用的情景分為兩種:

  1. 一個APP運行結束或者出錯之後的APP切換
  2. 在內核工作之後開始APP的載入和運行

2. L46-L51:這幾行彙編代碼特殊處理了哪些寄存器?這些寄存器的的值對於進入用戶態有何意義?請分別解釋。

代碼在此:

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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄Linux基本命令簡單認識shell認識命令的基本格式:內建命令與外部命令查看命令的類型-type查看命令的使用方法-helpmkdirpwdtouchecho認識路徑lscd認識熱鍵/linux熱鍵treenanocatgccstatrmrmdir基本認識--創建目錄許可權linux有多少條指令 ...
  • 這篇博客文章詳細介紹瞭如何在Windows和Ubuntu平臺上安裝和配置Syncthing文件同步工具。文章提供了從官方下載Syncthing的方法,並指導如何在Windows上解壓並啟動Syncthing服務,以及如何在Ubuntu上使用Xshell和Xftp上傳Syncthing文件,並通過命令... ...
  • 本文指出在工作中運用 Windows 遠程桌面工具時,因安全與隱私因素,有時需刪除連接的歷史記錄和憑據。文中給出了一個相關的 PowerShell 腳本,還說明瞭其使用方法,涵蓋運行 PowerShell 的條件、CredentialManager 模塊的安裝、腳本的執行流程及輸入選擇等,同時提到了... ...
  • 1、背景描述 如上圖所示,根路徑“/”所在的文件系統已沒有可用的磁碟空間,需要擴容磁碟。 df -h 2、VirtualBox操作 2.1、查看當前虛擬磁碟的大小 如上圖所示,點擊打開選中的虛擬機的 Settings 界面。 如上圖所示,當前虛擬機的虛擬磁碟大小為 8GB 。 2.2、修改虛擬磁碟的 ...
  • 本文詳細介紹了在 CentOS 和 Ubuntu 系統上安裝 Nginx 的全過程,包括下載方法、安裝步驟、配置開機自啟以及基礎配置等重要內容,還提供了常見問題的解決方案和優化配置示例,助您順利搭建高效的伺服器環境。 ...
  • 眾所周知,WSL 2 為 Windows 用戶提供了一個強大、高效且靈活的 Linux 環境,特別適合開發者使用。它結合了 Windows 和 Linux 的優點,為用戶提供了更加全面和高效的工作環境。但缺點也很明顯,那就是預設安裝在本來空間就不富裕的C盤。 本次我們在非C盤的盤符快速安裝... ...
  • Keepalived是Linux下一個輕量級別的高可用解決方案。高可用:廣義來講,是指整個系統的高可用行;狹義的來講就是主機的冗餘和接管。 ...
  • 1、OrthoFinder 教程:用於比較基因組學的系統發育直系學推斷 1.1 orthofinder介紹 OrthoFinder是一種快速、準確和全面的比較基因組學分析工具。它可以找到直系和正群,為所有的正群推斷基因樹,併為所分析的物種推斷一個有根的物種樹。OrthoFinder還為比較基因組分析 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...