diff --git a/Cargo.lock b/Cargo.lock index a317d0d..d3adb25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,9 +9,23 @@ dependencies = [ "bootloader", "lazy_static", "spin", - "volatile", + "uart_16550", + "volatile 0.2.7", + "x86_64", ] +[[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bootloader" version = "0.9.19" @@ -33,8 +47,35 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "uart_16550" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad019480ef5ff8ffe66d6f6a259cd87cf317649481394981db1739d844f374" +dependencies = [ + "bitflags", + "x86_64", +] + [[package]] name = "volatile" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" + +[[package]] +name = "volatile" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c2dbd44eb8b53973357e6e207e370f0c1059990df850aca1eca8947cf464f0" + +[[package]] +name = "x86_64" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc6ed1ed2cd4536b083c34041aff7b84448ee25ac4aa5e9d54802ce226f9815" +dependencies = [ + "bit_field", + "bitflags", + "volatile 0.4.4", +] diff --git a/Cargo.toml b/Cargo.toml index 63a09b8..bc76f58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,17 +4,33 @@ version = "0.1.0" authors = ["Sainnhe Park "] edition = "2021" -[profile.dev] -panic = "abort" # 禁用 panic 时的栈展开 - -[profile.release] -panic = "abort" # 禁用 panic 时的栈展开 +# 只能先把这部分注释掉,因为 cargo 还没有修复这个 bug +# https://github.com/rust-lang/cargo/issues/7359 +# [profile.dev] +# panic = "abort" # 禁用 panic 时的栈展开 +# +# [profile.release] +# panic = "abort" # 禁用 panic 时的栈展开 [dependencies] bootloader = "0.9.8" volatile = "0.2.6" spin = "0.5.2" +x86_64 = "0.14.2" +uart_16550 = "0.2.0" [dependencies.lazy_static] version = "1.0" features = ["spin_no_std"] + +[package.metadata.bootimage] +test-args = [ + "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", + "-display", "none" +] +test-success-exit-code = 33 +test-timeout = 300 # 设定 300 秒后超时 + +[[test]] +name = "should_panic" +harness = false # 禁用 test runner, 让 should_panic 作为一个常规的可执行文件存在 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6bfd3ea --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,69 @@ +#![no_std] +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; +pub mod serial; +pub mod vga_buffer; + +pub trait Testable { + fn run(&self) -> (); +} + +impl Testable for T +where + T: Fn(), +{ + fn run(&self) { + serial_print!("{}...\t", core::any::type_name::()); // 打印函数名 + self(); // 执行这个函数。由于 T 具有 Fn() trait 所以它能够作为一个函数被直接调用 + serial_println!("[ok]"); // 打印 "[ok]" + } +} + +pub fn test_runner(tests: &[&dyn Testable]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test.run(); + } + exit_qemu(QemuExitCode::Success); +} + +pub fn test_panic_handler(info: &PanicInfo) -> ! { + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + exit_qemu(QemuExitCode::Failed); + loop {} +} + +#[cfg(test)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + test_main(); + loop {} +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + test_panic_handler(info) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +pub fn exit_qemu(exit_code: QemuExitCode) { + use x86_64::instructions::port::Port; + + unsafe { + let mut port = Port::new(0xf4); // iobase port + port.write(exit_code as u32); + } +} + diff --git a/src/main.rs b/src/main.rs index 7b1a594..7fdc618 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,11 @@ #![no_std] // 不链接 Rust 标准库 #![no_main] // 禁用 main 入口点,因为没有运行时 +#![feature(custom_test_frameworks)] +#![test_runner(anos::test_runner)] +#![reexport_test_harness_main = "test_main"] use core::panic::PanicInfo; - -mod vga_buffer; - -// 定义一个 byte string 类型的静态变量 -static HELLO: &[u8] = b"Hello World!"; +use anos::println; #[no_mangle] // 不重整函数名,否则编译器可能会生成名为 _ZN3blog_os4_start7hb173fedf945531caE 的函数 // extern "C" 告诉编译器这个函数应当使用 C 语言的调用约定 @@ -15,9 +14,14 @@ static HELLO: &[u8] = b"Hello World!"; pub extern "C" fn _start() -> ! { println!("Hello World{}", "!"); + #[cfg(test)] + test_main(); + loop {} } +// 在非测试模式下,将 panic 信息打印到 VGA 缓冲区 +#[cfg(not(test))] #[panic_handler] // panic! 时将会进行栈展开,执行各种 drop 函数来回收垃圾 // 禁用标准库之后,这些东西就都没有了,我们需要把它重写一遍 @@ -25,3 +29,9 @@ fn panic(info: &PanicInfo) -> ! { println!("{}", info); loop {} } + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + anos::test_panic_handler(info) +} diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..6c6d65f --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,32 @@ +use uart_16550::SerialPort; +use spin::Mutex; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref SERIAL1: Mutex = { + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; + serial_port.init(); + Mutex::new(serial_port) + }; +} + +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { + use core::fmt::Write; + SERIAL1.lock().write_fmt(args).expect("Printing to serial failed"); +} + +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! serial_println { + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( + concat!($fmt, "\n"), $($arg)*)); +} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 2b7518d..37e1654 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -125,38 +125,25 @@ impl fmt::Write for Writer { } } -lazy_static! { - pub static ref WRITER: Mutex = Mutex::new(Writer { +// 将 Writer 设置为静态以便调用 +lazy_static! { // 内置的 static 会用到标准库里的一些东西,所以这里用 lazy_static + pub static ref WRITER: Mutex = Mutex::new(Writer { // 用互斥锁来防止冲突 column_position: 0, color_code: ColorCode::new(Color::Yellow, Color::Black), buffer: unsafe { &mut *(0xb8000 as *mut Buffer) } }); } -pub fn print_something() { - use core::fmt::Write; - let mut writer = Writer { - column_position: 0, - color_code: ColorCode::new(Color::Yellow, Color::Black), - // VGA Buffer 从 0xb8000 开始 - buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, - }; - writer.write_byte(b'H'); - writer.write_string("ello "); - writer.write_string("Wörld!"); - write!(writer, "The numbers are {} and {}", 42, 1.0/3.0).unwrap(); -} - // 将宏导出到 crate 的根,这样我们就可以用 // use crate::println // 来使用它 #[macro_export] -macro_rules! print { +macro_rules! print { // print!() ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); } #[macro_export] -macro_rules! println { +macro_rules! println { // println!() () => ($crate::print!("\n")); ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } @@ -168,3 +155,25 @@ pub fn _print(args: fmt::Arguments) { use core::fmt::Write; WRITER.lock().write_fmt(args).unwrap(); } + +#[test_case] +fn test_println_simple() { // 测试单行 println! + println!("test_println_simple output"); +} + +#[test_case] +fn test_println_many() { // 测试多行 println! + for _ in 0..100 { + println!("test_println_many output"); + } +} + +#[test_case] +fn test_println_output() { // 测试字符是否真的打印到了屏幕上 + let s = "Some test string that fits on a single line"; + println!("{}", s); + for (i, c) in s.chars().enumerate() { + let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read(); + assert_eq!(char::from(screen_char.ascii_character), c); + } +} diff --git a/tests/basic_boot.rs b/tests/basic_boot.rs new file mode 100644 index 0000000..7477812 --- /dev/null +++ b/tests/basic_boot.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(anos::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; +use anos::println; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + test_main(); + + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + anos::test_panic_handler(info) +} + +#[test_case] +fn test_println() { + println!("test_println output"); +} diff --git a/tests/should_panic.rs b/tests/should_panic.rs new file mode 100644 index 0000000..36d4d0c --- /dev/null +++ b/tests/should_panic.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use anos::{QemuExitCode, exit_qemu, serial_println, serial_print}; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + serial_println!("\t[ok]"); + exit_qemu(QemuExitCode::Success); + loop {} +} + +fn should_fail() { + serial_print!("should_panic::should_fail..."); + assert_eq!(0, 1); +} + +#[no_mangle] +pub extern "C" fn _start() -> ! { + should_fail(); + serial_println!("[test did not panic]"); + exit_qemu(QemuExitCode::Failed); + loop {} +}