rcore-os / rCore-Tutorial

Tutorial for rCore OS step by step (3rd edition)
https://rcore-os.github.io/rCore-Tutorial-deploy/
GNU General Public License v3.0
462 stars 68 forks source link

关于 os/src/sbi.rs::shutdown 后 qemu 和 gdb 问题以及相关一些探索和疑问 #127

Open generalibm opened 3 years ago

generalibm commented 3 years ago

问题描述

下载最新代码后,切换到 lab-1 分支,make run 出现如下错误信息:

...
src/main.rs:44: 'end of rust_main'
sbi_trap_error: hart0: trap handler failed (error -2)
sbi_trap_error: hart0: mcause=0x0000000000000007 mtval=0x0000000000100000
sbi_trap_error: hart0: mepc=0x0000000080003d3e mstatus=0x8000000000007800
sbi_trap_error: hart0: ra=0x0000000080008228 sp=0x000000008001ec98
sbi_trap_error: hart0: gp=0x0000000000000000 tp=0x000000008001f000
sbi_trap_error: hart0: s0=0x000000008001eca8 s1=0x0000000000000040
sbi_trap_error: hart0: a0=0x0000000000000000 a1=0x0000000080003d2a
sbi_trap_error: hart0: a2=0x0000000080003d2a a3=0x0000000080003d2a
sbi_trap_error: hart0: a4=0x0000000000100000 a5=0x0000000000005555
sbi_trap_error: hart0: a6=0x0000000000003d2a a7=0x000000008000e0e0
sbi_trap_error: hart0: s2=0x0000000000000000 s3=0x000000008001f000
sbi_trap_error: hart0: s4=0x0000000000000000 s5=0x0000000000000000
sbi_trap_error: hart0: s6=0x0000000000000001 s7=0x0000000000000000
sbi_trap_error: hart0: s8=0x0000000000000000 s9=0x0000000000000000
sbi_trap_error: hart0: s10=0x0000000000000000 s11=0x0000000000000008
sbi_trap_error: hart0: t0=0x0000000000000000 t1=0x0000000000000000
sbi_trap_error: hart0: t2=0x000000008021622c t3=0x0000000000000000
sbi_trap_error: hart0: t4=0x0000000000000000 t5=0x0000000000000000
sbi_trap_error: hart0: t6=0x0000000000000000

之后,gbd 和 qemu 陷入卡死状态,目前我的处理方式是 kill -9

我的环境

Tool Version
mac OS 11.1
rustc 1.46.0-nightly
qemu-system-riscv64 5.1.0
openSBI 0.7
SBI 0.2

我的追踪过程

  1. 根据 os/src/main.rs:44: 'end of rust_main' 提示,追踪到 os/src/panic.rs::panic_handler:26shutdown

  2. os/src/sbi.rs:44shutdown 调用 sbi_call(8,0,0,0)。根据 os/src/sbi.rs:7 的定义,发现是使用了 ecall 请求 M 态(即SBI)服务,在这里是 qemu 下的 openSBI v0.7,该版本实现了 RISCV SBI 的 v0.2 版本。

  3. 自然而言会想到查看 SBI 手册,在这里找到了关于 shutdown 的说明。起初,看到 Replacement EID 这一列时,认为可能是我的机器环境比实验手册中环境要新一些,故阅读完该文档之后,实现了一个新版本的 sbi_shutdown 取代此前os/src/panic.rs::panic_handler:26处的 shutdown,代码如下:

    
    #[inline(always)]
    fn sbi_call(eid: usize,fid: usize,  arg0: usize, arg1: usize, arg2: usize) -> Sbiret {
    let mut error;
    let mut value;
    unsafe {
        llvm_asm!("ecall"
            : "={x10}" (error), "={x11}" (value)
            : "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (eid), "{x16}" (fid)
            : "memory"      // 如果汇编可能改变内存,则需要加入 memory 选项
            : "volatile");     // 防止编译器做激进的优化(如调换指令顺序等破坏 SBI 调用行为的优化)
    }
    Sbiret{error, value}
    }

pub struct Sbiret { pub error: i64, pub value: i64, }

const SBI_SYSTEM_RESET: usize = 0x53525354;

pub fn sbi_shutdown() -> Sbiret { sbi_call(SBI_SYSTEM_RESET, 0, 0, 0,0) }


  而后,在 `os/src/panic.rs::panic_handler:27` 处打印 `struct Sbiret` 的值,结果为 `sbi_ret.error = -2` 和 ` sbi_ret.value = 0`;根据[文档](https://github.com/riscv/riscv-sbi-doc/blob/master/riscv-sbi.adoc#binary-encoding),查到 `-2` 代表`SBI_ERR_NOT_SUPPORTED`,即 SBI v0.2 不支持这个用法。于是放弃了继续改写的打算,转而查找 `shutdown` 后 `sbi_trap_error: hart0: trap handler failed (error -2)` 原因。

---

4. 经过各种 搜索引擎 的对关键字 `sbi_trap_error` `trap handler failed` `error` 检索之后,发现相关内容数量少且关系不大。只好回到 rCore 本身。
---

5. 在 rCore 的 lab-1 代码中,对 `shutdown` 和 `sbi_call` 进行了断点调试,主要涉及 `os/src/panic.rs` 和 `os/src/sbi.rs` 两个文件,其中将 `os/src/interrupt/mod.rs:15` 即 `timer::init()` 注释掉了,因为这个时钟也是通过 `ecall` 来实现的,因此会多次影响调试时的程序运行栈。结果证明是个 `loop`,只好另谋方案。
---

6. 虽然在 rCore 代码上经过了一番努力和尝试之后还是无果,但是可以得出结论问题不在 rCore ,而是在 SBI ,于是,我把目光转向到 openSBI 的[实现]()上,希望能够找到一些思路。
---

7. 在 `opensbi/lib/sbi/sbi_trap.c::216` 中,终于看到了熟悉的 `trap handler failed` 的身影,根据函数名 `sbi_trap_handler` 以及该函数注释,不难发现该函数和熟悉的rCore上的 `sbi_call` 完美契合。在 `opensbi/lib/sbi/sbi_trap.c::267` 中调用了在同一个文件中的函数 `sbi_trap_redirect` ,此后将函数的返回值作为判断条件是否进行 `trap_error` 处理;在 `opensbi/lib/sbi/sbi_trap.c::sbi_trap_error` 中可以发现,这里的处理即是打印各种信息,而这和 rCore `make run` 之后的错误信息完全一致;此后进入 `sbi_hart_hang`, 该函数内部是一个 `while (1)` 循环,这也就解释了 gdb 和 qemu 卡死的原因。因此,知道 `sbi_trap_redirect` 的返回值代表的含义就能发现问题的原因了。
--- 

8. 在 `sbi_trap_redirect` 中,返回值只有两个 `0` (`opensbi/lib/sbi/sbi_trap.c::194`) 和 `SBI_ENOTSUPP` (`opensbi/lib/sbi/sbi_trap.c::98`)。而根据 `make run` 的错误信息,以及 `opensbi/lib/sbi/sbi_trap.c:29` 不难知道这个不是 `0` 而是 `-2`,因此可以得出假设 `SBI_ENOTSUPP = 2`。
---

9. 果然在 `opensbi/include/sbi/sbi.error.h:19` 发现了 `#define SBI_ENOTSUPP    SBI_ERR_NOT_SUPPORTED`,又在 `opensbi/include/sbi/sbi_ecall/interface.h:88` 中发现 `#define SBI_ERR_NOT_SUPPORTED -2`。于是假设 `SBI_ENOTSUPP = 2` 得到了证实。原本找到错误的原因应该举杯欢庆,但是对 SBI_ERR_NOT_SUPPORTED 的字面意思又难以高兴起来,其意为 rCore 中的 `os/src/sbi.rs::shutdown` 即 `sbi_call(8,0,0,0)` 也是不支持。和最初想要自己动手写一个 `sbi_shutdown` 一样,openSBI v0.7 目前都不支持。被这个问题困扰了一段时间之后,接下来的思路就是再搭建一个环境。
---

10. 在搭建 virtual box 和 ubuntu18.04 的等待过程中,突然有了个疑问:之前看到的 SBI 文档是 v0.2 吗?应该是 master 分支上吧。于是,转向查找 [SBI v0.2 文档](https://github.com/riscv/riscv-sbi-doc/blob/v0.2.0/riscv-sbi.adoc)。
---

11. 在 SBI v0.2 文档中 [`sbi_shutdown`](https://github.com/riscv/riscv-sbi-doc/blob/v0.2.0/riscv-sbi.adoc#function-listing-1) 中,对于 `Replacement Extension` 并没有指定,因此最初重写 `sbi_shutdown` 后,返回值表明没有实现这个是合理的。但是,在 `Extension ID` 列中,关于 `sbi_shutdown` 指定的值确是 `0x08`,而经过上面的探究已经 openSBI 也给出了不支持的回答。至此,问题似乎明晰又不明晰。在看完 SBI v0.2 文档之后,发现 [HSM](https://github.com/riscv/riscv-sbi-doc/blob/v0.2.0/riscv-sbi.adoc#hart-state-management-extension-extension-id-0x48534d-hsm)实现 `sbi_hart_stop`,根据其解释 

> Returns ownership of the calling hart back to the SBI implementation

结合 `sbi_shutdown` 的解释

> Puts all the harts to shut down state from supervisor point of view.

似乎可以通过 `sbi_hart_stop` 实现 `sbi_shutdown`。

---
12. 于是,再次重写 `sbi_shutdown` 如下
```rust
const HSMMID:usize = 0x48534D;
const HSM_STOP_FID:usize = 1;

pub fn sbi_hart_stop() -> Sbiret {
    sbi_call(HSMMID, HSM_STOP_FID, 0, 0, 0)
}

然后,用 sbi_hart_stop 替代 os/src/panic.rs:26 处的 shutdown


  1. make run 之后光标停在了 src/main.rs:44: 'end of rust_main' 之后。至此,shutdown 问题得到了解决,但是不确定的是这个方案优劣几何。

  2. 既然,sbi_shutdownecall 对于 EID = 8 不支持,那么 EID = 0 的时钟是否也不支持呢?在将 os/src/mod.rs:15timer::init(); 解除注释后,再把 os/src/main.rs:44中的 panic("end of rust_main") 改为 loop {}后,make run 发现打印出各种 ticks ,说明 open SBI v0.7 对于 EID = 0 情况的 ecall 是支持的。

涉及文件

rCore-Tutorial 的 lab-1 下

相关段落

/// SBI 调用

[inline(always)]

fn sbi_call(eid: i32,fid: i32, arg0: usize, arg1: usize, arg2: usize) -> Sbiret { let mut error; let mut value; unsafe { llvm_asm!("ecall" : "={x10}" (error), "={x11}" (value) : "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (eid), "{x16}" (fid) : "memory" // 如果汇编可能改变内存,则需要加入 memory 选项 : "volatile"); // 防止编译器做激进的优化(如调换指令顺序等破坏 SBI 调用行为的优化) } Sbiret{error, value} }

pub struct Sbiret { pub error: i64, pub value: i64, }

const SBI_HSM_STOP_EID:i32 = 0x48534D; const SBI_HSM_STOP_FID:i32 = 1;

const SBI_SET_TIMER: i32 = 0; const SBI_CONSOLE_PUTCHAR: i32 = 1; const SBI_CONSOLE_GETCHAR: i32 = 2; const SBI_CLEAR_IPI: i32 = 3; const SBI_SEND_IPI: i32 = 4; const SBI_REMOTE_FENCE_I: i32 = 5; const SBI_REMOTE_SFENCE_VMA: i32 = 6; const SBI_REMOTE_SFENCE_VMA_ASID: i32 = 7; const SBI_SHUTDOWN: i32 = 8;

/// 向控制台输出一个字符 /// /// 需要注意我们不能直接使用 Rust 中的 char 类型 pub fn console_putchar(c: usize) { sbi_call(SBI_CONSOLE_PUTCHAR, 0, c, 0, 0); }

/// 从控制台中读取一个字符 /// /// 没有读取到字符则返回 -1 pub fn console_getchar() -> Sbiret { sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0, 0) }

/// 调用 SBI_SHUTDOWN 来关闭操作系统(直接退出 QEMU) pub fn shutdown() -> ! { sbi_call(SBI_SHUTDOWN, 0, 0, 0,0); unreachable!() }

/// 设置下一次时钟中断的时间 pub fn set_timer(time: usize) { sbi_call(SBI_SET_TIMER, 0, time, 0,0); }

/// 关闭 hart ,等价于 SBI_SHUTDOWN ? /// TODO: need to verify pub fn sbi_hart_stop() -> Sbiret { sbi_call(SBI_HSM_STOP_EID, SBI_HSM_STOP_FID, 0, 0, 0) }


- 修改后的函数 `os/src/sbi.rs::panic_handler`
```rust
#[panic_handler]
fn panic_handler(info: &PanicInfo) -> ! {
    // `\x1b[??m` 是控制终端字符输出格式的指令,在支持的平台上可以改变文字颜色等等,这里使用红色
    // 参考:https://misc.flogisoft.com/bash/tip_colors_and_formatting
    //
    // 需要全局开启 feature(panic_info_message) 才可以调用 .message() 函数
    if let Some(location) = info.location() {
        println!(
            "\x1b[1;31m{}:{}: '{}'\x1b[0m",
            location.file(),
            location.line(),
            info.message().unwrap()
        );
    } else {
        println!("\x1b[1;31mpanic: '{}'\x1b[0m", info.message().unwrap());
    }

    // This call is not expected to return under normal conditions.
    // Returns SBI_ERR_FAILED through sbiret.error only if it fails, 
    // where SBI_ERR_FAILED = -1.
    let sbiret = sbi_hart_stop();
    println!("sbiret.error = {}, sbiret.value = {}", sbiret.error, sbiret.value);

    unreachable!()
}

遇到问题

对于本问题的分析如上。但是对于目前给出 shutdown 的方案仍然感到疑惑。

wyfcyx commented 3 years ago

看起来这是 OpenSBI 和 Qemu 的兼容性问题。 这是该项目的新一轮迭代截至第一章的代码,最终也调用了 SBI 的 shutdown 接口。这一版中,我们将 SBI 实现替换为 RustSBI,它完全使用 Rust 开发,更加简洁,且完全足以用于教学。你可以试试这个 ch1 分支在你原来的环境下可否正常运行。这是第一章的文档

generalibm commented 3 years ago

@wyfcyx 谢谢,在我的机器下的运行结果和文档中有些出入,具体说来,我这里运行的结果如下:

[rustsbi] Version 0.1.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|

[rustsbi] Platform: QEMU
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: 0x222
[rustsbi] medeleg: 0xb109
[rustsbi] Kernel entry: 0x80020000
Hello, world!
.text [0x80020000, 0x80022000)
.rodata [0x80022000, 0x80023000)
.data [0x80023000, 0x80023000)
boot_stack [0x80023000, 0x80033000)
.bss [0x80033000, 0x80033000)
Panicked at src/main.rs:46 Shutdown machine!
panicked at 'Unhandled exception! mcause: Exception(StoreFault), mepc: 000000008000261c, mtval: 0000000000100000', platform/qemu/src/main.rs:395:18

我暂时先发出这个结果,等研究完文档上的说明之后再查查什么原因。再次感谢!!!

generalibm commented 3 years ago

根据上述结果最后一行的信息,定位到了 RUSTSBI 原码处,再根据 platform/qemu/src/main.rs:355 解释,应当是 RUSTSBI 认为 rCore 在 shutdown 处发出的 ecall(8,0,0,0) 为非法指令。

目前的解决方法是,安装 qemu v5.0.0 。

备注:安装 qemu v5.0.0 之后的 rCore-Tutorial 和 rCore-Tutorial-v3 均能正常 shutdown 。

equation314 commented 2 years ago

最近在 qemu v5.1.0 上遇到了同样的问题,最后发现原因如下:

qemu 中 shutdown 的实现是让 guest OS 对 sifive,test1 设备进行 MMIO 写操作,在 qemu v5.1.0 中要求对该设备的 MMIO 必须是 32 位访问,而 OpenSBI 使用了 16 位的写操作 (这里),导致在 M 态发生 Store/AMO access fault 异常。

在 QEMU v5.2.0 之后该问题被修复,详见该补丁:https://github.com/qemu/qemu/commit/ab3d207fe89bc0c63739db19e177af49179aa457