diff --git a/Cargo.lock b/Cargo.lock index ef58c18c8..844fa608e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,6 +478,12 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "glob" version = "0.3.1" @@ -647,6 +653,7 @@ dependencies = [ "static_assertions", "tdx-guest", "trapframe", + "unwinding", "volatile", "x86", "x86_64", @@ -1446,6 +1453,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "unwinding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a19a21a537f635c16c7576f22d0f2f7d63353c1337ad4ce0d8001c7952a25b" +dependencies = [ + "gimli", +] + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index edd6d08ac..607b718f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,18 @@ x86_64 = "0.14.2" jinux-time = { path = "services/comps/time" } jinux-framebuffer = { path = "services/comps/framebuffer" } +[profile.dev] +opt-level = 0 +debug = true +lto = false +panic = "unwind" + +[profile.release] +opt-level = 3 +debug = false +lto = false +panic = "unwind" + [workspace] members = [ diff --git a/build.rs b/build.rs index 2c8643757..df176c1db 100644 --- a/build.rs +++ b/build.rs @@ -1,16 +1,19 @@ use std::{error::Error, path::PathBuf}; fn main() -> Result<(), Box> { - let linker_script_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("framework") - .join("jinux-frame") - .join("src") - .join("arch") - .join("x86") - .join("boot") - .join("linker.ld"); + let target = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let linker_script_path = if target == "x86_64" { + PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("framework") + .join("jinux-frame") + .join("src") + .join("arch") + .join("x86") + .join("linker.ld") + } else { + panic!("Unsupported target arch: {}", target); + }; println!("cargo:rerun-if-changed={}", linker_script_path.display()); println!("cargo:rustc-link-arg=-T{}", linker_script_path.display()); - println!("cargo:rerun-if-env-changed=CARGO_PKG_NAME"); Ok(()) } diff --git a/framework/jinux-frame/Cargo.toml b/framework/jinux-frame/Cargo.toml index 182084521..294237fd6 100644 --- a/framework/jinux-frame/Cargo.toml +++ b/framework/jinux-frame/Cargo.toml @@ -6,24 +6,25 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bitflags = "1.3" -cfg-if = "1.0" -spin = "0.9.4" -volatile = { version = "0.4.5", features = ["unstable"] } -buddy_system_allocator = "0.9.0" -pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" } align_ext = { path = "../libs/align_ext" } -ktest = { path = "../libs/ktest" } -intrusive-collections = "0.9.5" -log = "0.4" -lazy_static = { version = "1.0", features = ["spin_no_std"] } -trapframe = { git = "https://github.com/jinzhao-dev/trapframe-rs", rev = "9758a83" } -inherit-methods-macro = { git = "https://github.com/jinzhao-dev/inherit-methods-macro", rev = "98f7e3e" } -tdx-guest = { path = "../libs/tdx-guest", optional = true } -bitvec = { version = "1.0", default-features = false, features = ["alloc"] } -static_assertions = "1.1.0" -int-to-c-enum = { path = "../../services/libs/int-to-c-enum" } bit_field = "0.10.1" +bitflags = "1.3" +bitvec = { version = "1.0", default-features = false, features = ["alloc"] } +buddy_system_allocator = "0.9.0" +cfg-if = "1.0" +inherit-methods-macro = { git = "https://github.com/jinzhao-dev/inherit-methods-macro", rev = "98f7e3e" } +int-to-c-enum = { path = "../../services/libs/int-to-c-enum" } +intrusive-collections = "0.9.5" +ktest = { path = "../libs/ktest" } +lazy_static = { version = "1.0", features = ["spin_no_std"] } +log = "0.4" +pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" } +spin = "0.9.4" +static_assertions = "1.1.0" +tdx-guest = { path = "../libs/tdx-guest", optional = true } +trapframe = { git = "https://github.com/jinzhao-dev/trapframe-rs", rev = "9758a83" } +unwinding = { version = "0.2.1", default-features = false, features = ["fde-static", "hide-trace", "panic", "personality", "unwinder"] } +volatile = { version = "0.4.5", features = ["unstable"] } [target.x86_64-custom.dependencies] x86_64 = "0.14.2" diff --git a/framework/jinux-frame/src/arch/x86/boot/linker.ld b/framework/jinux-frame/src/arch/x86/linker.ld similarity index 83% rename from framework/jinux-frame/src/arch/x86/boot/linker.ld rename to framework/jinux-frame/src/arch/x86/linker.ld index f05898bcd..c2e6d0205 100644 --- a/framework/jinux-frame/src/arch/x86/boot/linker.ld +++ b/framework/jinux-frame/src/arch/x86/linker.ld @@ -21,20 +21,23 @@ SECTIONS . += KERNEL_VMA; - .text : AT(ADDR(.text) - KERNEL_VMA) { *(.text .text.*) } + .text : AT(ADDR(.text) - KERNEL_VMA) { + *(.text .text.*) + PROVIDE(__etext = .); + } .rodata : AT(ADDR(.rodata) - KERNEL_VMA) { *(.rodata .rodata.*) } .eh_frame_hdr : AT(ADDR(.eh_frame_hdr) - KERNEL_VMA) { - __eh_frame_hdr = .; - KEEP(*(.eh_frame_hdr)) - __eh_frame_hdr_end = .; + KEEP(*(.eh_frame_hdr .eh_frame_hdr.*)) } + . = ALIGN(8); .eh_frame : AT(ADDR(.eh_frame) - KERNEL_VMA) { - __eh_frame = .; - KEEP(*(.eh_frame)) - __eh_frame_end = .; + PROVIDE(__eh_frame = .); + KEEP(*(.eh_frame .eh_frame.*)) } + .gcc_except_table : AT(ADDR(.gcc_except_table) - KERNEL_VMA) { *(.gcc_except_table .gcc_except_table.*) } + .data.rel.ro : AT(ADDR(.data.rel.ro) - KERNEL_VMA) { *(.data.rel.ro .data.rel.ro.*) } .dynamic : AT(ADDR(.dynamic) - KERNEL_VMA) { *(.dynamic) } diff --git a/framework/jinux-frame/src/arch/x86/mod.rs b/framework/jinux-frame/src/arch/x86/mod.rs index 9a553a59c..478868b65 100644 --- a/framework/jinux-frame/src/arch/x86/mod.rs +++ b/framework/jinux-frame/src/arch/x86/mod.rs @@ -7,6 +7,7 @@ pub(crate) mod irq; pub(crate) mod kernel; pub(crate) mod mm; pub(crate) mod pci; +pub mod qemu; #[cfg(feature = "intel_tdx")] pub(crate) mod tdx_guest; pub(crate) mod timer; diff --git a/framework/jinux-frame/src/arch/x86/qemu.rs b/framework/jinux-frame/src/arch/x86/qemu.rs new file mode 100644 index 000000000..609bd0f84 --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/qemu.rs @@ -0,0 +1,23 @@ +//! QEMU isa debug device. + +/// The exit code of x86 QEMU isa debug device. In `qemu-system-x86_64` the +/// exit code will be `(code << 1) | 1`. So you could never let QEMU invoke +/// `exit(0)`. We also need to check if the exit code is returned by the +/// kernel, so we couldn't use 0 as exit_success because this may conflict +/// with QEMU return value 1, which indicates that QEMU itself fails. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x20, +} + +pub fn exit_qemu(exit_code: QemuExitCode) -> ! { + use x86_64::instructions::port::Port; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } + unreachable!() +} diff --git a/framework/jinux-frame/src/lib.rs b/framework/jinux-frame/src/lib.rs index dcdace9f1..8a32caed6 100644 --- a/framework/jinux-frame/src/lib.rs +++ b/framework/jinux-frame/src/lib.rs @@ -1,21 +1,22 @@ //! The framework part of Jinux. -#![no_std] -#![allow(dead_code)] -#![allow(unused_variables)] -#![feature(negative_impls)] -#![feature(fn_traits)] -#![feature(const_maybe_uninit_zeroed)] #![feature(alloc_error_handler)] -#![feature(core_intrinsics)] -#![feature(new_uninit)] -#![feature(strict_provenance)] +#![feature(const_maybe_uninit_zeroed)] +#![feature(const_mut_refs)] +#![feature(const_ptr_sub_ptr)] #![feature(const_trait_impl)] +#![feature(core_intrinsics)] +#![feature(fn_traits)] #![feature(generators)] #![feature(iter_from_generator)] -#![feature(const_mut_refs)] #![feature(let_chains)] +#![feature(negative_impls)] +#![feature(new_uninit)] +#![feature(panic_info_message)] #![feature(ptr_sub_ptr)] -#![feature(const_ptr_sub_ptr)] +#![feature(strict_provenance)] +#![allow(dead_code)] +#![allow(unused_variables)] +#![no_std] extern crate alloc; #[macro_use] @@ -32,6 +33,7 @@ pub mod cpu; mod error; pub mod io_mem; pub mod logger; +pub mod panicking; pub mod prelude; pub mod sync; pub mod task; @@ -44,14 +46,8 @@ pub mod vm; pub use self::cpu::CpuLocal; pub use self::error::Error; pub use self::prelude::Result; -use alloc::vec::Vec; -use arch::irq::{IrqCallbackHandle, IrqLine}; -use core::{mem, panic::PanicInfo}; #[cfg(feature = "intel_tdx")] use tdx_guest::init_tdx; -use trapframe::TrapFrame; - -static mut IRQ_CALLBACK_LIST: Vec = Vec::new(); pub fn init() { arch::before_all_init(); @@ -69,19 +65,10 @@ pub fn init() { trap::init(); arch::after_all_init(); bus::init(); - register_irq_common_callback(); - invoke_c_init_funcs(); + invoke_ffi_init_funcs(); } -fn register_irq_common_callback() { - unsafe { - for i in 0..256 { - IRQ_CALLBACK_LIST.push(IrqLine::acquire(i as u8).on_active(general_handler)) - } - } -} - -fn invoke_c_init_funcs() { +fn invoke_ffi_init_funcs() { extern "C" { fn __sinit_array(); fn __einit_array(); @@ -95,86 +82,17 @@ fn invoke_c_init_funcs() { } } -fn general_handler(trap_frame: &TrapFrame) { - // info!("general handler"); - // println!("{:#x?}", trap_frame); - // println!("rip = 0x{:x}", trap_frame.rip); - // println!("rsp = 0x{:x}", trap_frame.rsp); - // println!("cr2 = 0x{:x}", trap_frame.cr2); - // // println!("rbx = 0x{:x}", trap_frame.) - // panic!("couldn't handler trap right now"); -} - -#[inline(always)] -pub(crate) const fn zero() -> T { - unsafe { mem::MaybeUninit::zeroed().assume_init() } -} - -/// The panic handler provided by Jinux Frame. -/// -/// The definition of the real panic handler is located at the kernel binary -/// crate with the `#[panic_handler]` attribute. This function provides a -/// default implementation of the panic handler, which can forwarded to by the -/// kernel binary crate. -/// -/// ```rust -/// extern crate jinux_frame; -/// #[panic_handler] -/// fn panic(info: &PanicInfo) -> ! { -/// jinux_frame::panic_handler(info); -/// } -/// ``` -pub fn panic_handler(info: &PanicInfo) -> ! { - println!("[panic]:{:#?}", info); - // let mut fp: usize; - // let stop = unsafe{ - // Task::current().kstack.get_top() - // }; - // info!("stop:{:x}",stop); - // unsafe{ - // asm!("mov rbp, {}", out(reg) fp); - // info!("fp:{:x}",fp); - // println!("---START BACKTRACE---"); - // for i in 0..10 { - // if fp == stop { - // break; - // } - // println!("#{}:ra={:#x}", i, *((fp - 8) as *const usize)); - // info!("fp target:{:x}",*((fp ) as *const usize)); - // fp = *((fp - 16) as *const usize); - // } - // println!("---END BACKTRACE---"); - // } - exit_qemu(QemuExitCode::Failed); -} - -/// The exit code of x86 QEMU isa debug device. In `qemu-system-x86_64` the -/// exit code will be `(code << 1) | 1`. So you could never let QEMU invoke -/// `exit(0)`. We also need to check if the exit code is returned by the -/// kernel, so we couldn't use 0 as exit_success because this may conflict -/// with QEMU return value 1, which indicates that QEMU itself fails. -#[cfg(target_arch = "x86_64")] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum QemuExitCode { - Success = 0x10, - Failed = 0x20, -} - -pub fn exit_qemu(exit_code: QemuExitCode) -> ! { - use x86_64::instructions::port::Port; - - unsafe { - let mut port = Port::new(0xf4); - port.write(exit_code as u32); - } - unreachable!() -} - +/// Unit test for the ktest framework and functions of the frame. #[if_cfg_ktest] mod test { #[ktest] fn trivial_assertion() { assert_eq!(0, 0); } + + #[ktest] + #[should_panic] + fn failing_assertion() { + assert_eq!(0, 1); + } } diff --git a/framework/jinux-frame/src/panicking.rs b/framework/jinux-frame/src/panicking.rs new file mode 100644 index 000000000..527c7db26 --- /dev/null +++ b/framework/jinux-frame/src/panicking.rs @@ -0,0 +1,38 @@ +//! Panic support in Jinux Frame. + +use alloc::boxed::Box; +use alloc::string::{String, ToString}; + +use crate::arch::qemu::{exit_qemu, QemuExitCode}; +use crate::println; + +#[derive(Clone, Debug)] +pub struct PanicInfo { + pub message: String, + pub file: String, + pub line: usize, + pub col: usize, +} + +impl core::fmt::Display for PanicInfo { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + writeln!(f, "Panicked at {}:{}:{}", self.file, self.line, self.col)?; + writeln!(f, "{}", self.message) + } +} + +#[panic_handler] +pub fn panic_handler(info: &core::panic::PanicInfo) -> ! { + let throw_info = PanicInfo { + message: info.message().unwrap().to_string(), + file: info.location().unwrap().file().to_string(), + line: info.location().unwrap().line() as usize, + col: info.location().unwrap().column() as usize, + }; + // Throw an exception and expecting it to be caught. + unwinding::panic::begin_panic(Box::new(throw_info.clone())); + // If the exception is not caught (e.g. by ktest), then print the information + // and exit failed using the debug device. + println!("[uncaught panic] {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/framework/libs/ktest/src/lib.rs b/framework/libs/ktest/src/lib.rs index 00a09dab0..47ac74303 100644 --- a/framework/libs/ktest/src/lib.rs +++ b/framework/libs/ktest/src/lib.rs @@ -21,6 +21,11 @@ //! fn trivial_assertion() { //! assert_eq!(0, 0); //! } +//! #[ktest] +//! #[should_panic] +//! fn failing_assertion() { +//! assert_eq!(0, 1); +//! } //! } //! ``` //! @@ -54,8 +59,8 @@ //! a default conditional compilation setting: //! `#[cfg(all(ktest, any(ktest = "all", ktest = #crate_name)))]` //! -//! Currently we do not support `#[should_panic]` attribute, and this feature will -//! be added in the future. +//! We do not support `#[should_panic]` attribute, but the implementation is quite +//! slow currently. Use it with cautious. //! //! Doctest is not taken into consideration yet, and the interface is subject to //! change. @@ -128,27 +133,43 @@ pub fn ktest(_attr: TokenStream, item: TokenStream) -> TokenStream { proc_macro2::Span::call_site(), ); + let should_panic = input.attrs.iter().any(|attr| { + attr.path() + .segments + .iter() + .any(|segment| segment.ident == "should_panic") + }); + + let package_name = std::env::var("CARGO_PKG_NAME").unwrap(); let span = proc_macro::Span::call_site(); - let source = span.source_file(); - let crate_name = std::env::var("CARGO_PKG_NAME").unwrap(); - let hint_str = format!( - "[{}] {}: {}()", - crate_name, - source.path().to_str().unwrap(), - fn_name - ); + let source = span.source_file().path(); + let source = source.to_str().unwrap(); + let line = span.line(); + let col = span.column(); let register = quote! { struct #ktest_item_struct { fn_: fn() -> (), - hint: &'static str, + should_panic: bool, + module_path: &'static str, + fn_name: &'static str, + package: &'static str, + source: &'static str, + line: usize, + col: usize, } #[cfg(ktest)] #[used] #[link_section = ".ktest_array"] static #fn_ktest_item_name: #ktest_item_struct = #ktest_item_struct { fn_: #fn_name, - hint: #hint_str, + should_panic: #should_panic, + module_path: module_path!(), + fn_name: stringify!(#fn_name), + package: #package_name, + source: #source, + line: #line, + col: #col, }; }; @@ -165,27 +186,70 @@ pub fn ktest(_attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro] pub fn do_ktests(_item: TokenStream) -> TokenStream { let body = quote! { + use crate::arch::qemu::{exit_qemu, QemuExitCode}; + struct KtestItem { fn_: fn() -> (), - hint: &'static str, + should_panic: bool, + module_path: &'static str, + fn_name: &'static str, + package: &'static str, + source: &'static str, + line: usize, + col: usize, }; extern "C" { fn __ktest_array(); fn __ktest_array_end(); - } + }; let item_size = core::mem::size_of::() as u64; let l = (__ktest_array_end as u64 - __ktest_array as u64) / item_size; crate::println!("Running {} tests", l); for i in 0..l { unsafe { - let address = (__ktest_array as u64 + item_size * i) as *const u64; - let item = address as *const KtestItem; - crate::print!("{} ...", (*item).hint); - ((*item).fn_)(); + let item_ptr = (__ktest_array as u64 + item_size * i) as *const u64; + let item = item_ptr as *const KtestItem; + crate::print!("[{}] test {}::{} ...", (*item).package, (*item).module_path, (*item).fn_name); + let test_result = unwinding::panic::catch_unwind((*item).fn_); + let print_failure_heading = || { + crate::println!("\nfailures:\n"); + crate::println!("---- {}:{}:{} - {} ----", (*item).source, (*item).line, (*item).col, (*item).fn_name); + }; + if !(*item).should_panic { + match test_result { + Ok(()) => { + crate::println!(" ok"); + }, + Err(e) => { + crate::println!(" FAILED"); + print_failure_heading(); + match e.downcast::() { + Ok(s) => { + crate::println!("[caught panic] {}", s); + }, + Err(payload) => { + crate::println!("[caught panic] unknown panic payload: {:#?}", payload); + }, + } + exit_qemu(QemuExitCode::Failed); + }, + } + } else { + match test_result { + Ok(()) => { + crate::println!(" FAILED"); + print_failure_heading(); + crate::println!("test did not panic as expected"); + exit_qemu(QemuExitCode::Failed); + }, + Err(_) => { + crate::println!(" ok"); + }, + } + } } - crate::println!(" Ok!"); } - crate::exit_qemu(crate::QemuExitCode::Success); + exit_qemu(QemuExitCode::Success); }; TokenStream::from(body) diff --git a/kernel/main.rs b/kernel/main.rs index c685fde05..b9bf3cd82 100644 --- a/kernel/main.rs +++ b/kernel/main.rs @@ -1,23 +1,17 @@ #![no_std] #![no_main] -// The `no_mangle`` attribute for the `jinux_main` entrypoint requires the removal of safety check. +// The `export_name` attribute for the `jinux_main` entrypoint requires the removal of safety check. // Please be aware that the kernel is not allowed to introduce any other unsafe operations. // #![forbid(unsafe_code)] extern crate jinux_frame; -use core::panic::PanicInfo; use jinux_frame::println; -#[no_mangle] -pub fn jinux_main() -> ! { +#[export_name = "jinux_main"] +pub fn main() -> ! { jinux_frame::init(); println!("[kernel] finish init jinux_frame"); component::init_all(component::parse_metadata!()).unwrap(); jinux_std::init(); jinux_std::run_first_process(); } - -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - jinux_frame::panic_handler(info); -} diff --git a/services/libs/jinux-std/src/lib.rs b/services/libs/jinux-std/src/lib.rs index a0f8b2883..ee4246b0f 100644 --- a/services/libs/jinux-std/src/lib.rs +++ b/services/libs/jinux-std/src/lib.rs @@ -27,7 +27,10 @@ use crate::{ Thread, }, }; -use jinux_frame::{boot, exit_qemu, QemuExitCode}; +use jinux_frame::{ + arch::qemu::{exit_qemu, QemuExitCode}, + boot, +}; use process::Process; extern crate alloc; diff --git a/services/libs/jinux-std/src/vm/vmo/options.rs b/services/libs/jinux-std/src/vm/vmo/options.rs index beabfef31..a1291eb98 100644 --- a/services/libs/jinux-std/src/vm/vmo/options.rs +++ b/services/libs/jinux-std/src/vm/vmo/options.rs @@ -531,9 +531,6 @@ mod test { } #[ktest] - // FIXME: should_panic doesn't work with ktest, two negative makes a positive... - #[should_panic] - /// FIXME: alloc continuous frames is not supported now fn alloc_continuous_vmo() { let vmo = VmoOptions::::new(10 * PAGE_SIZE) .flags(VmoFlags::CONTIGUOUS) diff --git a/x86_64-custom.json b/x86_64-custom.json index cd9ab4256..0ee93158c 100644 --- a/x86_64-custom.json +++ b/x86_64-custom.json @@ -11,7 +11,6 @@ "executables": true, "linker-flavor": "ld.lld", "linker": "rust-lld", - "panic-strategy": "abort", "disable-redzone": true, "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float" }