Skip to content

Commit 8f3b08a

Browse files
committed
feat(test): 实现内核测试框架的上下文切换机制
1 parent 705a322 commit 8f3b08a

File tree

4 files changed

+118
-11
lines changed

4 files changed

+118
-11
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ jobs:
3535
restore-keys: |
3636
${{ runner.os }}-cargo-
3737
38-
- name: Install anaxa-builder
39-
run: cargo install cargo-anaxa
40-
4138
- name: Run cargo test
4239
working-directory: kernel
4340
run: cargo test --lib

kernel/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ macro_rules! extern_safe {
108108
mod tests {
109109

110110
#[test_case]
111-
fn test_extern_safe_macro() {
111+
fn test_success() {
112112
assert_eq!(2 + 3, 5);
113113
}
114+
#[test_case]
115+
fn test_failure() {
116+
assert_eq!(2 + 3, 6);
117+
}
114118
}

kernel/src/panic.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ pub fn panic(info: &PanicInfo) -> ! {
2525
// This is the panic handler for all testing function
2626
#[cfg(test)]
2727
pub fn panic_for_test(info: &PanicInfo) -> ! {
28-
serial_println!("failed");
28+
serial_println!("[FAILED]");
2929
serial_println!("Caused by:\n\t{}", info);
30-
crate::test::exit_qemu(crate::test::QemuExitCode::Failed);
31-
loop {} // Unreachable, but must write this
30+
crate::test::long_jmp();
3231
}

kernel/src/test.rs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,92 @@
44
//! This provides the test trait and runner.
55
66
use crate::{serial_print, serial_println};
7+
use core::arch::asm;
8+
use spin::Mutex;
9+
10+
/// The Jump Buffer to save the CPU state.
11+
#[derive(Debug, Clone, Copy, Default)]
12+
#[repr(C)]
13+
pub struct JumpBuffer {
14+
rbx: u64,
15+
rsp: u64,
16+
rbp: u64,
17+
r12: u64,
18+
r13: u64,
19+
r14: u64,
20+
r15: u64,
21+
rip: u64,
22+
}
23+
24+
static TEST_JMP_BUF: Mutex<Option<JumpBuffer>> = Mutex::new(None);
25+
static FAIL_COUNT: Mutex<usize> = Mutex::new(0);
26+
27+
/// Save the current context into the jump buffer.
28+
/// Returns 0 when saving, and 1 when returning from long_jmp.
29+
#[unsafe(no_mangle)]
30+
pub unsafe extern "C" fn set_jmp() -> u64 {
31+
let mut jmp_buf = JumpBuffer::default();
32+
let res: u64;
33+
34+
asm!(
35+
// 保存寄存器状态
36+
"mov [rcx + 0], rbx",
37+
"mov [rcx + 8], rsp",
38+
"mov [rcx + 16], rbp",
39+
"mov [rcx + 24], r12",
40+
"mov [rcx + 32], r13",
41+
"mov [rcx + 40], r14",
42+
"mov [rcx + 48], r15",
43+
// 保存返回地址
44+
"lea rdx, [rip + 2f]",
45+
"mov [rcx + 56], rdx",
46+
// 首次调用返回 0
47+
"mov rax, 0",
48+
"jmp 3f",
49+
// longjmp 返回点
50+
"2:",
51+
// longjmp 后返回 1
52+
"mov rax, 1",
53+
"3:",
54+
in("rcx") &mut jmp_buf,
55+
out("rax") res,
56+
out("rdx") _,
57+
clobber_abi("sysv64")
58+
);
59+
60+
if res == 0 {
61+
*TEST_JMP_BUF.lock() = Some(jmp_buf);
62+
}
63+
res
64+
}
65+
66+
/// Restore the context and jump back to the set_jmp location.
67+
pub fn long_jmp() -> ! {
68+
let jmp_buf = TEST_JMP_BUF.lock().expect("No jump buffer set!");
69+
70+
// 在恢复寄存器前增加错误计数
71+
// 这样错误计数不会因为寄存器恢复而被覆盖
72+
let mut fail_count = FAIL_COUNT.lock();
73+
*fail_count += 1;
74+
drop(fail_count); // 释放锁,防止死锁
75+
76+
unsafe {
77+
asm!(
78+
// 恢复寄存器状态
79+
"mov rbx, [rcx + 0]",
80+
"mov rsp, [rcx + 8]",
81+
"mov rbp, [rcx + 16]",
82+
"mov r12, [rcx + 24]",
83+
"mov r13, [rcx + 32]",
84+
"mov r14, [rcx + 40]",
85+
"mov r15, [rcx + 48]",
86+
// 跳转回保存的地址
87+
"jmp [rcx + 56]",
88+
in("rcx") &jmp_buf,
89+
options(noreturn)
90+
);
91+
}
92+
}
793

894
/// The trait that assign the function is testable.
995
pub trait Testable {
@@ -17,19 +103,40 @@ where
17103
T: Fn(),
18104
{
19105
fn run(&self) {
20-
serial_print!("Testing {}...\t", core::any::type_name::<T>());
21-
self();
22-
serial_println!("[ok]");
106+
serial_print!("Testing {}... ", core::any::type_name::<T>());
107+
108+
// 使用 setjmp/longjmp 进行测试
109+
unsafe {
110+
if set_jmp() == 0 {
111+
// 第一次执行测试
112+
self();
113+
serial_println!("[OK]");
114+
} else {
115+
// longjmp 返回,测试失败
116+
// [FAILED] 已在 panic 处理程序中打印
117+
}
118+
}
23119
}
24120
}
25121

26122
/// This is the test runner, which will run if the test begins.
27123
pub fn test_runner(tests: &[&dyn Testable]) {
28124
serial_println!("Running {} tests", tests.len());
125+
29126
for test in tests {
30127
test.run();
31128
}
32-
exit_qemu(QemuExitCode::Success);
129+
130+
let final_fail_count = *FAIL_COUNT.lock();
131+
serial_println!("Total failures: {}", final_fail_count);
132+
133+
if final_fail_count > 0 {
134+
serial_println!("\n[DONE] FAILED: {} tests failed", final_fail_count);
135+
exit_qemu(QemuExitCode::Failed);
136+
} else {
137+
serial_println!("\n[DONE] SUCCESS: All tests passed!");
138+
exit_qemu(QemuExitCode::Success);
139+
}
33140
}
34141

35142
/// This is the QEMU exit code

0 commit comments

Comments
 (0)