rCore-Tutorial
rCore-Tutorial copied to clipboard
关于 os/src/sbi.rs::shutdown 后 qemu 和 gdb 问题以及相关一些探索和疑问
问题描述
下载最新代码后,切换到 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 |
我的追踪过程
- 根据
os/src/main.rs:44: 'end of rust_main'提示,追踪到os/src/panic.rs::panic_handler:26中shutdown;
- 在
os/src/sbi.rs:44中shutdown调用sbi_call(8,0,0,0)。根据os/src/sbi.rs:7的定义,发现是使用了ecall请求 M 态(即SBI)服务,在这里是 qemu 下的 openSBI v0.7,该版本实现了 RISCV SBI 的 v0.2 版本。
- 自然而言会想到查看 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;根据文档,查到 -2 代表SBI_ERR_NOT_SUPPORTED,即 SBI v0.2 不支持这个用法。于是放弃了继续改写的打算,转而查找 shutdown 后 sbi_trap_error: hart0: trap handler failed (error -2) 原因。
- 经过各种 搜索引擎 的对关键字
sbi_trap_errortrap handler failederror检索之后,发现相关内容数量少且关系不大。只好回到 rCore 本身。
- 在 rCore 的 lab-1 代码中,对
shutdown和sbi_call进行了断点调试,主要涉及os/src/panic.rs和os/src/sbi.rs两个文件,其中将os/src/interrupt/mod.rs:15即timer::init()注释掉了,因为这个时钟也是通过ecall来实现的,因此会多次影响调试时的程序运行栈。结果证明是个loop,只好另谋方案。
- 虽然在 rCore 代码上经过了一番努力和尝试之后还是无果,但是可以得出结论问题不在 rCore ,而是在 SBI ,于是,我把目光转向到 openSBI 的实现上,希望能够找到一些思路。
- 在
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中可以发现,这里的处理即是打印各种信息,而这和 rCoremake run之后的错误信息完全一致;此后进入sbi_hart_hang, 该函数内部是一个while (1)循环,这也就解释了 gdb 和 qemu 卡死的原因。因此,知道sbi_trap_redirect的返回值代表的含义就能发现问题的原因了。
- 在
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。
- 果然在
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 目前都不支持。被这个问题困扰了一段时间之后,接下来的思路就是再搭建一个环境。
- 在搭建 virtual box 和 ubuntu18.04 的等待过程中,突然有了个疑问:之前看到的 SBI 文档是 v0.2 吗?应该是 master 分支上吧。于是,转向查找 SBI v0.2 文档。
- 在 SBI v0.2 文档中
sbi_shutdown中,对于Replacement Extension并没有指定,因此最初重写sbi_shutdown后,返回值表明没有实现这个是合理的。但是,在Extension ID列中,关于sbi_shutdown指定的值确是0x08,而经过上面的探究已经 openSBI 也给出了不支持的回答。至此,问题似乎明晰又不明晰。在看完 SBI v0.2 文档之后,发现 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。
- 于是,再次重写
sbi_shutdown如下
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。
make run之后光标停在了src/main.rs:44: 'end of rust_main'之后。至此,shutdown问题得到了解决,但是不确定的是这个方案优劣几何。
- 既然,
sbi_shutdown中ecall对于EID = 8不支持,那么EID = 0的时钟是否也不支持呢?在将os/src/mod.rs:15的timer::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 下
- os/src/sbi.rs
- os/src/panic.rs
- os/src/main.rs
相关段落
- 修改后的文件
os/src/sbi.rs
//! 调用 Machine 层的操作
// 目前还不会用到全部的 SBI 调用,暂时允许未使用的变量或函数
#![allow(unused)]
/// 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
#[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 的方案仍然感到疑惑。
看起来这是 OpenSBI 和 Qemu 的兼容性问题。 这是该项目的新一轮迭代截至第一章的代码,最终也调用了 SBI 的 shutdown 接口。这一版中,我们将 SBI 实现替换为 RustSBI,它完全使用 Rust 开发,更加简洁,且完全足以用于教学。你可以试试这个 ch1 分支在你原来的环境下可否正常运行。这是第一章的文档。
@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
我暂时先发出这个结果,等研究完文档上的说明之后再查查什么原因。再次感谢!!!
根据上述结果最后一行的信息,定位到了 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 。
最近在 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