44//! This provides the test trait and runner.
55
66use 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.
995pub 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.
27123pub 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