diff --git a/.gitignore b/.gitignore index 1b9259f6..cdd41dc9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,9 @@ *.hdd *.img *.txt +*.od /target +.vscode .DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3d3b0181..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rust-analyzer.cargo.target": "x86_64-unknown-none", - "rust-analyzer.check.allTargets": false -} \ No newline at end of file diff --git a/kernel/limage_config.toml b/kernel/limage_config.toml index c971a2d8..4a25dab9 100644 --- a/kernel/limage_config.toml +++ b/kernel/limage_config.toml @@ -29,7 +29,7 @@ base_args = [ # Graphics "-vga", "std", - +"-serial", "stdio", # Debugging traces # USB # "-trace", "usb*", @@ -44,7 +44,7 @@ base_args = [ [modes] terminal = { args = ["-nographic"] } -gui = { args = [] } +gui = { args = ["-serial", "stdio"] } gdb-terminal = { args = ["-nographic", "-s", "-S"] } gdb-gui = { args = ["-s", "-S"] } @@ -54,6 +54,5 @@ success_exit_code = 33 no_reboot = true extra_args = [ "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", - "-serial", "stdio", "-display", "none" ] diff --git a/kernel/src/constants/processes.rs b/kernel/src/constants/processes.rs index 869f6b80..87696aaf 100644 --- a/kernel/src/constants/processes.rs +++ b/kernel/src/constants/processes.rs @@ -1,8 +1,6 @@ //! Process related constants // Binaries used for tests -pub const PRINT_EXIT: &[u8] = include_bytes!("../processes/test_binaries/print_exit"); - pub const FORK_SIMPLE: &[u8] = include_bytes!("../processes/test_binaries/fork_simple"); pub const TEST_SIMPLE_PROCESS: &[u8] = @@ -14,6 +12,8 @@ pub const TEST_PRINT_EXIT: &[u8] = include_bytes!("../processes/test_binaries/te pub const TEST_WAIT: &[u8] = include_bytes!("../processes/test_binaries/test_wait"); +pub const TEST_SLEEP: &[u8] = include_bytes!("../processes/test_binaries/sleep"); + pub const TEST_FORK_COW: &[u8] = include_bytes!("../processes/test_binaries/test_fork_cow"); pub const TEST_MMAP_ANON_SHARED: &[u8] = diff --git a/kernel/src/constants/syscalls.rs b/kernel/src/constants/syscalls.rs index a73c032c..5390fdec 100644 --- a/kernel/src/constants/syscalls.rs +++ b/kernel/src/constants/syscalls.rs @@ -2,12 +2,37 @@ pub const SYSCALL_EXIT: u32 = 60; pub const SYSCALL_NANOSLEEP: u32 = 35; -pub const SYSCALL_PRINT: u32 = 3; + +pub const SYSCALL_PRINT: u32 = 1003; + +pub const SYSCALL_READ: u32 = 0; +pub const SYSCALL_WRITE: u32 = 1; +pub const SYSCALL_OPEN: u32 = 2; +pub const SYSCALL_CLOSE: u32 = 3; + +pub const SYSCALL_ACCESS: u32 = 21; + +pub const SYSCALL_SCHED_YIELD: u32 = 24; + +pub const SYSCALL_CREAT: u32 = 85; + +pub const SYSCALL_FUTEX: u32 = 202; +pub const SYSCALL_OPENAT: u32 = 257; + +pub const SYSCALL_GETRANDOM: u32 = 318; + pub const SYSCALL_MMAP: u32 = 4; pub const SYSCALL_MPROTECT: u32 = 10; pub const SYSCALL_MUNMAP: u32 = 11; pub const SYSCALL_FORK: u32 = 5; pub const SYSCALL_WAIT: u32 = 6; +pub const SYSCALL_EXECVE: u32 = 59; + +pub const SYSCALL_GETUID: u32 = 102; +pub const SYSCALL_GETEUID: u32 = 107; +pub const SYSCALL_GETGID: u32 = 104; +pub const SYSCALL_GETEGID: u32 = 108; +pub const SYSCALL_ARCH_PRCTL: u32 = 158; // Mmap pub const START_MMAP_ADDRESS: u64 = 0x0900_0000_0000; diff --git a/kernel/src/devices/ps2_dev/keyboard.rs b/kernel/src/devices/ps2_dev/keyboard.rs index 5371fe84..3529a30c 100644 --- a/kernel/src/devices/ps2_dev/keyboard.rs +++ b/kernel/src/devices/ps2_dev/keyboard.rs @@ -5,7 +5,7 @@ use crate::{ devices::ps2_dev::controller, - events::{futures::sync::BlockMutex, schedule_kernel}, + events::{futures::sync::BlockMutex, schedule_kernel, yield_now}, interrupts::idt::without_interrupts, serial_println, }; @@ -19,6 +19,7 @@ use lazy_static::lazy_static; use pc_keyboard::{ layouts, DecodedKey, Error, HandleControl, KeyCode, KeyState, Keyboard, Modifiers, ScancodeSet2, }; +use ps2::flags::ControllerStatusFlags; use spin::Mutex; /// Maximum number of keyboard events to store in the buffer @@ -258,6 +259,25 @@ pub fn keyboard_handler() { }); } +pub async fn flush_buffer() { + loop { + if let Ok(mut kb) = KEYBOARD.try_lock() { + // got the lock, clear it + kb.clear_buffer(); + controller::with_controller(|ctrl| { + while ctrl + .read_status() + .contains(ControllerStatusFlags::OUTPUT_FULL) + { + let _ = ctrl.read_data(); + } + }); + return; + } + yield_now().await; + } +} + impl Stream for KeyboardStream { type Item = KeyboardEvent; diff --git a/kernel/src/events/event.rs b/kernel/src/events/event.rs index 7525e1b5..c44b829e 100644 --- a/kernel/src/events/event.rs +++ b/kernel/src/events/event.rs @@ -24,6 +24,10 @@ impl Event { completed: AtomicBool::new(false), } } + + pub fn id(&self) -> u64 { + self.eid.0 + } } impl ArcWake for Event { diff --git a/kernel/src/events/event_runner.rs b/kernel/src/events/event_runner.rs index 03cf7d2f..07a17252 100644 --- a/kernel/src/events/event_runner.rs +++ b/kernel/src/events/event_runner.rs @@ -46,7 +46,6 @@ impl EventRunner { if !self.have_unblocked_events() { break; } - self.current_event = self.next_event(); let event = self @@ -66,6 +65,7 @@ impl EventRunner { // Executes the event let ready: bool = future_guard.as_mut().poll(&mut context) != Poll::Pending; + interrupts::disable(); drop(future_guard); @@ -84,6 +84,8 @@ impl EventRunner { // Event is ready, go ahead and remove it event.completed.swap(true, Ordering::Relaxed); self.pending_events.write().remove(&event.eid.0); + + crate::debug!("Event {} done", event.eid.0); } } @@ -92,6 +94,8 @@ impl EventRunner { // Explicitly re-enable interrupts once the current event is unmarked // Helps with run_process_ring3(), which shouldn't be pre-empted // TODO can maybe refactor and safely remove + + // TODO do a lil work-stealing interrupts::enable(); } @@ -100,8 +104,6 @@ impl EventRunner { self.awake_next_sleeper(); } - // TODO do a lil work-stealing - // Sleep until next interrupt interrupts::enable_and_hlt(); } @@ -259,6 +261,12 @@ impl EventRunner { self.current_event.as_ref().map(|e| { let system_ticks = nanos_to_ticks(nanos); + crate::debug!( + "Sleeping @ {}, to be awoken @ {}", + self.system_clock, + self.system_clock + system_ticks + ); + let sleep = Sleep::new(self.system_clock + system_ticks, (*e).clone()); self.sleeping_events.push(sleep.clone()); self.blocked_events.write().insert(e.eid.0); @@ -305,6 +313,15 @@ impl EventRunner { } } + /// Blocks the current event until awoken + /// + /// # Returns + /// * `Option` - A Block future that can be awaited on (if there is an event to block) + #[allow(dead_code)] + pub fn block_current_event(&mut self) -> Option { + todo!(); + } + /// # Returns /// * `bool` - true if there are blocked events on this runner fn have_blocked_events(&self) -> bool { diff --git a/kernel/src/events/tasks/yield_task.rs b/kernel/src/events/tasks/yield_task.rs index f22487b0..9f8c6c8b 100644 --- a/kernel/src/events/tasks/yield_task.rs +++ b/kernel/src/events/tasks/yield_task.rs @@ -23,12 +23,11 @@ impl Default for Yield { impl Future for Yield { type Output = (); - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { if self.yielded { Poll::Ready(()) } else { self.yielded = true; - cx.waker().wake_by_ref(); Poll::Pending } } diff --git a/kernel/src/filesys/ext2/filesystem.rs b/kernel/src/filesys/ext2/filesystem.rs index 6ee15517..29539e14 100644 --- a/kernel/src/filesys/ext2/filesystem.rs +++ b/kernel/src/filesys/ext2/filesystem.rs @@ -322,10 +322,11 @@ impl Ext2 { let size = node.size() as usize; let mut buffer = vec![0; size]; - node.read_at(pos as u64, &mut buffer) + let s = node + .read_at(pos as u64, &mut buffer) .await .map_err(FilesystemError::NodeError)?; - + buffer.resize(s, 0); Ok(buffer) } diff --git a/kernel/src/filesys/ext2/node.rs b/kernel/src/filesys/ext2/node.rs index 199f2957..ffacdfdf 100644 --- a/kernel/src/filesys/ext2/node.rs +++ b/kernel/src/filesys/ext2/node.rs @@ -495,6 +495,7 @@ impl Node { async fn read_raw_at(&self, offset: u64, buffer: &mut [u8]) -> NodeResult { let size = self.size(); + if offset >= size { return Ok(0); } diff --git a/kernel/src/filesys/ext2/structures.rs b/kernel/src/filesys/ext2/structures.rs index c7e29e40..a6363398 100644 --- a/kernel/src/filesys/ext2/structures.rs +++ b/kernel/src/filesys/ext2/structures.rs @@ -317,7 +317,7 @@ mod tests { #[test_case] async fn test_superblock_block_size() { let mut sb = Superblock::default(); - + // sb.block_size_shift = 0; // 1024 bytes assert_eq!(sb.block_size(), 1024); sb.block_size_shift = 1; // 2048 bytes diff --git a/kernel/src/filesys/mod.rs b/kernel/src/filesys/mod.rs index ee081996..cecfd339 100644 --- a/kernel/src/filesys/mod.rs +++ b/kernel/src/filesys/mod.rs @@ -5,7 +5,11 @@ use crate::{ constants::{memory::PAGE_SIZE, processes::MAX_FILES}, - events::{current_running_event, futures::sync::Condition, schedule_kernel_on}, + events::{ + current_running_event, + futures::sync::{BlockMutex, Condition}, + schedule_kernel_on, + }, filesys::ext2::structures::FileMode, memory::{ frame_allocator::{alloc_frame, dealloc_frame}, @@ -21,7 +25,10 @@ use alloc::{ sync::Arc, vec::Vec, }; -use core::sync::atomic::{AtomicBool, Ordering}; +use core::{ + cmp, + sync::atomic::{AtomicBool, Ordering}, +}; use ext2::{ filesystem::{Ext2, FilesystemError, FilesystemResult}, node::{DirEntry, NodeError}, @@ -33,6 +40,7 @@ use x86_64::{ VirtAddr, }; pub mod ext2; +pub mod syscalls; use async_trait::async_trait; @@ -45,7 +53,7 @@ lazy_static! { static ref FS_INIT_COMPLETE: Arc = Arc::new(AtomicBool::new(false)); /// Global filesystem instance - pub static ref FILESYSTEM: Once> = Once::new(); + pub static ref FILESYSTEM: Once> = Once::new(); } bitflags! { @@ -106,7 +114,7 @@ bitflags::bitflags! { /// A file object in the filesystem trait pub struct File { /// The pathname in the filesystem - pathname: String, + pub pathname: String, /// File descriptor pub fd: usize, /// Position we are seeking from in the file @@ -257,7 +265,7 @@ pub struct Ext2Wrapper { pub page_cache: PageCache, // Wrapper for Ext2 Filesystem - filesystem: Mutex, + pub filesystem: Mutex, // Maps inode number to number of processes refcount: Mutex>, @@ -498,11 +506,21 @@ impl FileSystem for Ext2Wrapper { let mut remaining = buf.len(); let mut total_read = 0; let mut file_pos = locked_file.position; - while remaining > 0 { let page_offset = file_pos & !(PAGE_SIZE - 1); let page_offset_in_buf = file_pos % PAGE_SIZE; let copy_len = core::cmp::min(PAGE_SIZE - page_offset_in_buf, remaining); + if file_pos as u64 + >= self + .filesystem + .lock() + .get_node(&locked_file.pathname) + .await + .expect("Failed to get path") + .size() + { + return Ok(total_read); + } // Load the page into cache if not already present let virt = match self .page_cache_get_mapping(locked_file.clone(), page_offset) @@ -510,15 +528,23 @@ impl FileSystem for Ext2Wrapper { { Ok(va) => va, Err(_) => { - self.add_entry_to_page_cache(locked_file.clone(), page_offset) - .await?; + match self + .add_entry_to_page_cache(locked_file.clone(), page_offset) + .await + { + Ok(_) => {} + Err(FilesystemError::CacheError) => { + return Ok(total_read); + } + Err(e) => return Err(e.into()), + } + let temp = self .page_cache_get_mapping(locked_file.clone(), page_offset) .await?; temp } }; - unsafe { let page_ptr = virt.as_ptr::().add(page_offset_in_buf); let dst_ptr = buf.as_mut_ptr().add(total_read); @@ -577,6 +603,15 @@ impl FileSystem for Ext2Wrapper { } let inode_number = file.inode_number; + // clone the pathname before await + let path = file.pathname.clone(); + + // read file buffer + let file_buf = self.filesystem.lock().read_file_at(&path, offset).await?; + if file_buf.len() == 0 { + return Err(FilesystemError::CacheError); + } + let mut pg_cache = self.page_cache.lock(); pg_cache .entry(inode_number) @@ -587,16 +622,15 @@ impl FileSystem for Ext2Wrapper { let frame = alloc_frame().ok_or(FilesystemError::CacheError)?; let default_flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; let kernel_va = map_kernel_frame(&mut *KERNEL_MAPPER.lock(), frame, default_flags); - // clone the pathname before await - let path = file.pathname.clone(); - - // read file buffer - let file_buf = self.filesystem.lock().read_file_at(&path, offset).await?; // Do raw pointer write *after* .await to avoid Send violation unsafe { let buf_ptr = kernel_va.as_mut_ptr(); - core::ptr::copy_nonoverlapping(file_buf.as_ptr(), buf_ptr, file_buf.len()); + core::ptr::copy_nonoverlapping( + file_buf.as_ptr(), + buf_ptr, + cmp::min(PAGE_SIZE, file_buf.len()), + ); } file_mappings.insert(offset, Page::containing_address(kernel_va)); @@ -631,7 +665,6 @@ impl FileSystem for Ext2Wrapper { pub fn init(cpu_id: u32) { serial_println!("INITING FS"); if cpu_id == 0 { - serial_println!("CPU ID 0"); schedule_kernel_on( 0, async { @@ -780,7 +813,8 @@ mod tests { { let meta = user_fs.metadata(fd).await.unwrap(); assert_eq!(meta.pathname, "./temp/meta.txt"); - assert_eq!(meta.fd, 0); + // 0 and 1 are stdin/out + assert_eq!(meta.fd, 2); assert_eq!(meta.flags, OpenFlags::O_WRONLY | OpenFlags::O_CREAT); } diff --git a/kernel/src/filesys/syscalls.rs b/kernel/src/filesys/syscalls.rs new file mode 100644 index 00000000..c45e75b6 --- /dev/null +++ b/kernel/src/filesys/syscalls.rs @@ -0,0 +1,40 @@ +use core::ffi::CStr; + +use crate::syscalls::syscall_handlers::ConstUserPtr; + +use super::{FileSystem, OpenFlags, FILESYSTEM}; + +// For now, ignore mode (universal access permissions) +pub async fn sys_open(pathname: ConstUserPtr, flags: u32, _mode: u16) -> u64 { + let path_str = unsafe { + match CStr::from_ptr(pathname.0).to_str() { + Ok(v) => v, + Err(_) => { + return u64::MAX; // TODO set errno + } + } + }; + + let open_flags = match OpenFlags::from_bits(flags) { + Some(of) => of, + None => { + return u64::MAX; + } + }; + + let mut filelock = FILESYSTEM + .get() + .expect("Filesystem not initialized") + .lock() + .await; + + match (*filelock).open_file(path_str, open_flags).await { + Ok(fd) => fd as u64, + Err(_) => u64::MAX, + } +} + +pub async fn sys_creat(pathname: ConstUserPtr, mode: u16) -> u64 { + let flags = (OpenFlags::O_CREAT | OpenFlags::O_WRONLY | OpenFlags::O_TRUNC).bits(); + sys_open(pathname, flags, mode).await +} diff --git a/kernel/src/init.rs b/kernel/src/init.rs index ad7a6d0c..edbf1461 100644 --- a/kernel/src/init.rs +++ b/kernel/src/init.rs @@ -2,6 +2,7 @@ //! //! Handles the initialization of kernel subsystems and CPU cores. +use alloc::vec::Vec; use bytes::Bytes; use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use limine::{ @@ -9,12 +10,17 @@ use limine::{ smp::{Cpu, RequestFlags}, BaseRevision, }; +use x86_64::align_up; use crate::{ + constants::memory::PAGE_SIZE, debug, devices::{self}, - events::{register_event_runner, run_loop, spawn, yield_now}, - filesys::{self}, + events::{ + current_running_event, futures::await_on::AwaitProcess, get_runner_time, + register_event_runner, run_loop, schedule_kernel, schedule_process, spawn, yield_now, + }, + filesys::{self, get_file, FileSystem, OpenFlags, FILESYSTEM}, interrupts::{self, idt}, ipc::{ messages::Message, @@ -25,9 +31,13 @@ use crate::{ }, logging, memory::{self}, - net::get_ip_addr, - processes::{self}, - serial_println, trace, + processes::{self, process::create_process}, + serial_println, + syscalls::{ + block::spin_on, + memorymap::{sys_mmap, MmapFlags, ProtFlags}, + }, + trace, }; extern crate alloc; @@ -63,13 +73,97 @@ pub fn init() -> u32 { // Should be kept after devices in case logging gets complicated // Right now log writes to serial, but if it were to switch to VGA, this would be important logging::init(0); - get_ip_addr().unwrap(); + //get_ip_addr().unwrap(); processes::init(0); debug!("Waking cores"); let bsp_id = wake_cores(); idt::enable(); + + schedule_kernel( + async { + let fs = FILESYSTEM.get().unwrap(); + let fd = { + fs.lock() + .await + .open_file( + "/executables/ret", + OpenFlags::O_RDONLY | OpenFlags::O_WRONLY, + ) + .await + .expect("Could not open file") + }; + let file = get_file(fd).unwrap(); + let file_len = { + fs.lock() + .await + .filesystem + .lock() + .get_node(&file.lock().pathname) + .await + .unwrap() + .size() + }; + + spin_on(async { + sys_mmap( + 0x9000, + align_up(file_len, PAGE_SIZE as u64), + ProtFlags::PROT_EXEC.bits(), + MmapFlags::MAP_FILE.bits(), + fd as i64, + 0, + ) + .await; + }); + serial_println!("Reading file..."); + + let mut buffer = alloc::vec![0u8; file_len as usize]; + let bytes_read = { + fs.lock() + .await + .read_file(fd, &mut buffer) + .await + .expect("Failed to read file") + }; + + let buf = &buffer[..bytes_read]; + + serial_println!("Bytes read: {}", bytes_read); + + { + //fs.lock().await.seek_file(fd, 0).await; + } + serial_println!("Reading file..."); + let mut buffer = alloc::vec![0u8; file_len as usize]; + + let bytes_read = { + fs.lock() + .await + .read_file(fd, &mut buffer) + .await + .expect("Failed to read file") + }; + + let buf = &buffer[..bytes_read]; + let t2 = get_runner_time(0); + + serial_println!("Bytes read: {}", bytes_read); + + let pid = create_process(buf, Vec::new(), Vec::new()); + serial_println!("Creating process"); + schedule_process(pid); + let _waiter = AwaitProcess::new( + pid, + get_runner_time(3_000_000_000), + current_running_event().unwrap(), + ) + .await; + }, + 3, + ); + bsp_id } diff --git a/kernel/src/interrupts/idt.rs b/kernel/src/interrupts/idt.rs index 13a3b34c..58247361 100644 --- a/kernel/src/interrupts/idt.rs +++ b/kernel/src/interrupts/idt.rs @@ -15,6 +15,8 @@ use x86_64::{ structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}, }; +use crate::syscalls::block::{block_on, spin_on}; +#[allow(deprecated)] use crate::{ constants::{ idt::{KEYBOARD_VECTOR, MOUSE_VECTOR, SYSCALL_HANDLER, TIMER_VECTOR, TLB_SHOOTDOWN_VECTOR}, @@ -38,7 +40,7 @@ use crate::{ }, syscalls::{ memorymap::sys_mmap, - syscall_handlers::{block_on, sys_exit, sys_nanosleep_32, sys_print}, + syscall_handlers::{sys_exit, sys_nanosleep_32, sys_print}, }, }; @@ -205,7 +207,7 @@ extern "x86-interrupt" fn page_fault_handler( pt_flags, fd, } => { - block_on(async { + spin_on(async { handle_shared_file_mapping(page, &mut mapper, offset, pt_flags, fd).await }); } @@ -216,7 +218,7 @@ extern "x86-interrupt" fn page_fault_handler( pt_flags, fd, } => { - block_on(async { + spin_on(async { handle_private_file_mapping( page, &mut mapper, @@ -304,7 +306,8 @@ pub extern "x86-interrupt" fn naked_syscall_handler(_: InterruptStackFrame) { } #[no_mangle] -#[allow(unused_variables, unused_assignments)] // disable until args p2-6 are used +#[allow(unused_variables, unused_assignments, deprecated)] // disable until args p2-6 are used +#[deprecated] fn syscall_handler(rsp: u64) { let syscall_num: u32; let p1: u64; @@ -343,7 +346,10 @@ fn syscall_handler(rsp: u64) { if syscall_num == SYSCALL_EXIT { sys_exit(p1 as i64, &ForkingRegisters::default()); } else if syscall_num == SYSCALL_MMAP { - let val = sys_mmap(p1, p2, p3, p4, p5 as i64, p6); + let val = block_on( + sys_mmap(p1, p2, p3, p4, p5 as i64, p6), + &ForkingRegisters::default(), + ); unsafe { core::arch::asm!( "mov rax, {0}", @@ -391,11 +397,11 @@ extern "x86-interrupt" fn naked_timer_handler(_: InterruptStackFrame) { push rcx push rbx push rax - + cld mov rdi, rsp call timer_handler - + pop rax pop rbx pop rcx diff --git a/kernel/src/interrupts/x2apic.rs b/kernel/src/interrupts/x2apic.rs index 899a92c8..db082adb 100644 --- a/kernel/src/interrupts/x2apic.rs +++ b/kernel/src/interrupts/x2apic.rs @@ -34,8 +34,9 @@ const X2APIC_IA32_EFER: u32 = 0xC000_0080; const X2APIC_IA32_LSTAR: u32 = 0xC000_0082; const X2APIC_IA32_FMASK: u32 = 0xC000_0084; const X2APIC_IA32_STAR: u32 = 0xC000_0081; -const X2APIC_IA32_GSBASE: u32 = 0xC000_0101; +pub const X2APIC_IA32_GSBASE: u32 = 0xC000_0101; const X2APIC_IA32_KERNEL_GSBASE: u32 = 0xC000_0102; +pub const X2APIC_IA32_FS_BASE: u32 = 0xC000_0100; // TODO Add support for sysenter and sysret system calls // const X2APIC_IA32_SYSENTER_CS: u32 = 0x174; diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index ed3ebd75..214f1dbb 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -29,6 +29,8 @@ pub mod net; pub mod processes; pub mod syscalls; +pub mod shell; + pub use devices::serial; pub mod prelude { diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 93f4d58d..125dba4e 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -37,7 +37,11 @@ extern "C" fn _start() -> ! { debug!("BSP entering event loop"); - unsafe { run_loop(bsp_id) } + // unsafe { shell::init() }; + + unsafe { + run_loop(bsp_id); + } } /// Production panic handler. diff --git a/kernel/src/memory/page_fault.rs b/kernel/src/memory/page_fault.rs index a1ffb04c..a0aede7f 100644 --- a/kernel/src/memory/page_fault.rs +++ b/kernel/src/memory/page_fault.rs @@ -274,7 +274,7 @@ pub async fn handle_shared_file_mapping( let mut flags = pt_flags; flags.set(PageTableFlags::PRESENT, true); - let mut fs = FILESYSTEM.get().expect("could not get fs").lock(); + let mut fs = FILESYSTEM.get().expect("could not get fs").lock().await; let file = with_current_pcb(|pcb| { pcb.fd_table[fd] .as_ref() @@ -332,7 +332,7 @@ pub async fn handle_private_file_mapping( // Set VMA SHARED flag to false so it's COW vma.flags.set(VmAreaFlags::SHARED, false); - let mut fs = FILESYSTEM.get().expect("could not get fs").lock(); + let mut fs = FILESYSTEM.get().expect("could not get fs").lock().await; let file = with_current_pcb(|pcb| { pcb.fd_table[fd] .as_ref() diff --git a/kernel/src/memory/paging.rs b/kernel/src/memory/paging.rs index f976a894..5e965620 100644 --- a/kernel/src/memory/paging.rs +++ b/kernel/src/memory/paging.rs @@ -288,366 +288,366 @@ pub fn update_permissions(page: Page, mapper: &mut impl Mapper, flags: tlb_shootdown(page.start_address()); } -#[cfg(test)] -mod tests { - use super::*; - - use alloc::{sync::Arc, vec::Vec}; - use core::{ - ptr::{read_volatile, write_volatile}, - sync::atomic::{AtomicU64, Ordering}, - }; - use x86_64::{ - structures::paging::{mapper::TranslateError, Page, PageTableFlags, PhysFrame}, - VirtAddr, - }; - - // Import functions from the kernel memory module. - use crate::{ - constants::memory::PAGE_SIZE, - events::schedule_kernel_on, - memory::{ - mm::{Mm, VmAreaBackings, VmAreaFlags}, - KERNEL_MAPPER, - }, - processes::process::{get_current_pid, with_current_pcb, PROCESS_TABLE}, - }; - - // Used for TLB shootdown testcases. - static PRE_READ: AtomicU64 = AtomicU64::new(0); - static POST_READ: AtomicU64 = AtomicU64::new(0); - - /// Asynchronously reads a u64 value from the start address of the given page and stores it in PRE_READ. - async fn pre_read(page: Page) { - let value = unsafe { page.start_address().as_ptr::().read_volatile() }; - PRE_READ.store(value, Ordering::SeqCst); - } - - /// Asynchronously reads a u64 value from the start address of the given page and stores it in POST_READ. - async fn post_read(page: Page) { - let value = unsafe { page.start_address().as_ptr::().read_volatile() }; - POST_READ.store(value, Ordering::SeqCst); - } - - /// Tests that after a mapping is removed the page translation fails. - /// - /// This test creates a mapping for a given virtual page, removes it, and then verifies that - /// translating the page results in a `PageNotMapped` error. - #[test_case] - async fn test_remove_mapped_frame() { - let mut mapper = KERNEL_MAPPER.lock(); - let page: Page = Page::containing_address(VirtAddr::new(0x500000000)); - let _ = create_mapping(page, &mut *mapper, None); - - remove_mapped_frame(page, &mut *mapper); - - let translate_frame_error = mapper.translate_page(page); - - assert!(matches!( - translate_frame_error, - Err(TranslateError::PageNotMapped) - )); - } - - /// Tests that mapping a page returns the correct physical frame. - /// - /// A mapping is created for a test virtual page and its returned physical frame is then - /// verified by translating the page. Finally, the mapping is removed. - #[test_case] - async fn test_basic_map_and_translate() { - let mut mapper = KERNEL_MAPPER.lock(); - - // Use a test virtual page. - let page: Page = Page::containing_address(VirtAddr::new(0x400001000)); - let frame: PhysFrame = create_mapping(page, &mut *mapper, None); - - let translate_frame = mapper.translate_page(page).expect("Translation failed"); - - assert_eq!(frame, translate_frame); - - remove_mapped_frame(page, &mut *mapper); - } - - /// Tests that updating page permissions works as expected. - /// - /// A mapping is created for a given page and then its permissions are updated (e.g. to make it - /// read-only by removing the WRITABLE flag). The test then retrieves the page table entry (PTE) - /// and asserts that it contains the expected flags. - #[test_case] - async fn test_update_permissions() { - let mut mapper = KERNEL_MAPPER.lock(); - - let page: Page = Page::containing_address(VirtAddr::new(0x400002000)); - let _ = create_mapping(page, &mut *mapper, None); - - let flags = PageTableFlags::PRESENT; // Only present (read-only). - - update_permissions(page, &mut *mapper, flags); - - let flags = get_page_flags(page, &mut mapper).expect("Getting page table flags failed"); - - assert!(flags.contains(PageTableFlags::PRESENT)); - assert!(!flags.contains(PageTableFlags::WRITABLE)); - - remove_mapped_frame(page, &mut *mapper); - } - - /// Tests that contiguous mappings spanning multiple pages work correctly. - /// - /// This test allocates mappings for a contiguous region of 8 pages. Each page is mapped with - /// writable permissions, a distinct value is written to each page, and then the value is read - /// back to verify correctness. Finally, all mappings are removed. - #[test_case] // Uncomment to run this test. - async fn test_contiguous_mapping() { - let mut mapper = KERNEL_MAPPER.lock(); - - // Define a contiguous region spanning 8 pages. - let start_page: Page = Page::containing_address(VirtAddr::new(0x400004000)); - let num_pages = 8; - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - - let mut frames = Vec::new(); - for i in 0..num_pages { - let page = Page::from_start_address(start_page.start_address() + i * PAGE_SIZE as u64) - .expect("Invalid page address"); - let frame = create_mapping(page, &mut *mapper, Some(flags)); - frames.push((page, frame)); - } - - // Write and verify distinct values. - for (i, (page, _)) in frames.iter().enumerate() { - let ptr = page.start_address().as_mut_ptr::(); - unsafe { write_volatile(ptr, i as u64) }; - let val = unsafe { read_volatile(ptr) }; - assert_eq!(val, i as u64); - } - - // Cleanup: Unmap all pages. - for (page, _) in frames { - remove_mapped_frame(page, &mut *mapper); - } - } - - /// Tests that TLB shootdowns work correctly across cores. - /// - /// This test creates a mapping for a given page and writes a value to cache it on the current core. - /// It then schedules a read of that page on an alternate core (to load it into that core’s TLB cache). - /// After that, the mapping is updated to a new frame with new contents and the new value is written. - /// Finally, the test re-schedules a read on the alternate core and verifies that the new value is observed. - #[test_case] - async fn test_tlb_shootdowns_cross_core() { - const AP: u32 = 1; - const PRIORITY: usize = 3; - - // Create mapping and set a value on the current core. - let page: Page = Page::containing_address(VirtAddr::new(0x500000000)); - - { - let mut mapper = KERNEL_MAPPER.lock(); - let _ = create_mapping(page, &mut *mapper, None); - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(0xdead); - } - } - - // Mapping exists now and is cached for the first core. - - // Schedule a read on core 1 to load the page into its TLB cache. - schedule_kernel_on(AP, async move { pre_read(page).await }, PRIORITY); - - while PRE_READ.load(Ordering::SeqCst) == 0 { - core::hint::spin_loop(); - } - - { - let mut mapper = KERNEL_MAPPER.lock(); - - let new_frame = alloc_frame().expect("Could not find a new frame"); - - // Update the mapping so that a TLB shootdown is necessary. - update_mapping( - page, - &mut *mapper, - new_frame, - Some(PageTableFlags::PRESENT | PageTableFlags::WRITABLE), - ); - - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(0x42); - } - } - - // Schedule another read on core 1 to verify that the new value is visible. - schedule_kernel_on(AP, async move { post_read(page).await }, PRIORITY); - - while POST_READ.load(Ordering::SeqCst) == 0 { - core::hint::spin_loop(); - } - - assert_eq!(POST_READ.load(Ordering::SeqCst), 0x42); - - let mut mapper = KERNEL_MAPPER.lock(); - remove_mapped_frame(page, &mut *mapper); - } - - // Tests the copy-on-write (COW) mechanism for a mapped page. - // - // In a COW scenario, the page is initially mapped as read-only. A write to the page should - // trigger a fault that results in a new physical frame being allocated and mapped for that page. - // In this test, we simulate this behavior by: - // 1. Creating a mapping with read-only permissions. - // 2. Writing to the page which, triggers a page fault - // 3. Handling the page fault in our page fault handler - // 4. Verifying that the new frame is different from the initial one and that the written value is present. - #[test_case] - async fn test_copy_on_write() { - let mut mapper = KERNEL_MAPPER.lock(); - // Create a dummy PML4 frame. - // Locate the current process. - - const TEST_VALUE: u64 = 0x42; - - let page = Page::containing_address(VirtAddr::new(0x400003000)); - - let anon_area = Arc::new(VmAreaBackings::new()); - - with_current_pcb(|pcb| { - pcb.mm.with_vma_tree_mutable(|tree| { - let _vma1 = Mm::insert_vma( - tree, - page.start_address().as_u64(), - page.start_address().as_u64() + PAGE_SIZE as u64, - anon_area.clone(), - VmAreaFlags::WRITABLE, - 0, - 0, - ); - }); - }); - - // Create mapping with read-only permission to simulate a COW mapping. - let init_frame = create_mapping(page, &mut *mapper, Some(PageTableFlags::PRESENT)); - - // Write to the page. - // Triggers page fault - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(TEST_VALUE); - } - - // Now, translating the page should return the new frame. - let frame = mapper - .translate_page(page) - .expect("Translation after COW failed"); - - // The new frame should be different from the original frame. - assert_ne!(init_frame, frame); - - let read_value = unsafe { page.start_address().as_ptr::().read_volatile() }; - - assert_eq!(read_value, TEST_VALUE); - - // should not trigger a page fault, we should be able to write now - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(0x20); - } - - let new_frame = mapper - .translate_page(page) - .expect("Translation after COW failed"); - - // We already made this our own, no need to have done COW - assert_eq!(frame, new_frame); - - let read_value2 = unsafe { page.start_address().as_ptr::().read_volatile() }; - - assert_eq!(read_value2, 0x20); - - remove_mapped_frame(page, &mut *mapper); - } - - /// Tests the copy-on-write (COW) mechanism for a mapped page. - /// - /// In a COW scenario, the page is initially mapped as writable, and a full buffer is written - /// Then, the page is marked read only and the first byte in the buffer is written to. - /// This should trigger a page fault that does COW, but it should maintain the rest - /// of the values in the buffer. - #[test_case] - async fn test_copy_on_write_full() { - let mut mapper = KERNEL_MAPPER.lock(); - // Create a dummy PML4 frame. - // Locate the current process. - let pid = get_current_pid(); - let process = { - let process_table = PROCESS_TABLE.read(); - process_table - .get(&pid) - .expect("can't find pcb in process table") - .clone() - }; - const TEST_VALUE: u8 = 0x2; - let page = Page::containing_address(VirtAddr::new(0x400003000)); - let anon_area = Arc::new(VmAreaBackings::new()); - - unsafe { - (*process.pcb.get()).mm.with_vma_tree_mutable(|tree| { - let _vma1 = Mm::insert_vma( - tree, - page.start_address().as_u64(), - page.start_address().as_u64() + PAGE_SIZE as u64, - anon_area.clone(), - VmAreaFlags::WRITABLE, - 0, - 0, - ); - }); - } - - // Create mapping with read-only permission to simulate a COW mapping. - let _ = create_mapping( - page, - &mut *mapper, - Some(PageTableFlags::PRESENT | PageTableFlags::WRITABLE), - ); - - // Write 1s to the entire buffer - unsafe { - let buf_ptr = page.start_address().as_mut_ptr::(); - core::ptr::write_bytes(buf_ptr, 1, PAGE_SIZE); - } - - // Make it so we page fault on write - update_permissions(page, &mut *mapper, PageTableFlags::PRESENT); - - // Write to the page. - // In a real system, this would trigger a page fault to handle copy-on-write. - unsafe { - page.start_address() - .as_mut_ptr::() - .write_volatile(TEST_VALUE); - } - - // the cow should not have messed with any data in the buffer besides what - // we just wrote to - let read_value = unsafe { page.start_address().as_ptr::().read_volatile() }; - - assert_eq!(read_value, TEST_VALUE); - - unsafe { - let buf_ptr = page.start_address().as_ptr::(); - for i in 1..PAGE_SIZE { - let val = *buf_ptr.add(i); - assert_eq!(val, 1, "Byte at offset {i} is not 1 (found {val})"); - } - } - - remove_mapped_frame(page, &mut *mapper); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// +// use alloc::{sync::Arc, vec::Vec}; +// use core::{ +// ptr::{read_volatile, write_volatile}, +// sync::atomic::{AtomicU64, Ordering}, +// }; +// use x86_64::{ +// structures::paging::{mapper::TranslateError, Page, PageTableFlags, PhysFrame}, +// VirtAddr, +// }; +// +// // Import functions from the kernel memory module. +// use crate::{ +// constants::memory::PAGE_SIZE, +// events::schedule_kernel_on, +// memory::{ +// mm::{Mm, VmAreaBackings, VmAreaFlags}, +// KERNEL_MAPPER, +// }, +// processes::process::{get_current_pid, with_current_pcb, PROCESS_TABLE}, +// }; +// +// // Used for TLB shootdown testcases. +// static PRE_READ: AtomicU64 = AtomicU64::new(0); +// static POST_READ: AtomicU64 = AtomicU64::new(0); +// +// /// Asynchronously reads a u64 value from the start address of the given page and stores it in PRE_READ. +// async fn pre_read(page: Page) { +// let value = unsafe { page.start_address().as_ptr::().read_volatile() }; +// PRE_READ.store(value, Ordering::SeqCst); +// } +// +// /// Asynchronously reads a u64 value from the start address of the given page and stores it in POST_READ. +// async fn post_read(page: Page) { +// let value = unsafe { page.start_address().as_ptr::().read_volatile() }; +// POST_READ.store(value, Ordering::SeqCst); +// } +// +// /// Tests that after a mapping is removed the page translation fails. +// /// +// /// This test creates a mapping for a given virtual page, removes it, and then verifies that +// /// translating the page results in a `PageNotMapped` error. +// #[test_case] +// async fn test_remove_mapped_frame() { +// let mut mapper = KERNEL_MAPPER.lock(); +// let page: Page = Page::containing_address(VirtAddr::new(0x500000000)); +// let _ = create_mapping(page, &mut *mapper, None); +// +// remove_mapped_frame(page, &mut *mapper); +// +// let translate_frame_error = mapper.translate_page(page); +// +// assert!(matches!( +// translate_frame_error, +// Err(TranslateError::PageNotMapped) +// )); +// } +// +// /// Tests that mapping a page returns the correct physical frame. +// /// +// /// A mapping is created for a test virtual page and its returned physical frame is then +// /// verified by translating the page. Finally, the mapping is removed. +// #[test_case] +// async fn test_basic_map_and_translate() { +// let mut mapper = KERNEL_MAPPER.lock(); +// +// // Use a test virtual page. +// let page: Page = Page::containing_address(VirtAddr::new(0x400001000)); +// let frame: PhysFrame = create_mapping(page, &mut *mapper, None); +// +// let translate_frame = mapper.translate_page(page).expect("Translation failed"); +// +// assert_eq!(frame, translate_frame); +// +// remove_mapped_frame(page, &mut *mapper); +// } +// +// /// Tests that updating page permissions works as expected. +// /// +// /// A mapping is created for a given page and then its permissions are updated (e.g. to make it +// /// read-only by removing the WRITABLE flag). The test then retrieves the page table entry (PTE) +// /// and asserts that it contains the expected flags. +// #[test_case] +// async fn test_update_permissions() { +// let mut mapper = KERNEL_MAPPER.lock(); +// +// let page: Page = Page::containing_address(VirtAddr::new(0x400002000)); +// let _ = create_mapping(page, &mut *mapper, None); +// +// let flags = PageTableFlags::PRESENT; // Only present (read-only). +// +// update_permissions(page, &mut *mapper, flags); +// +// let flags = get_page_flags(page, &mut mapper).expect("Getting page table flags failed"); +// +// assert!(flags.contains(PageTableFlags::PRESENT)); +// assert!(!flags.contains(PageTableFlags::WRITABLE)); +// +// remove_mapped_frame(page, &mut *mapper); +// } +// +// /// Tests that contiguous mappings spanning multiple pages work correctly. +// /// +// /// This test allocates mappings for a contiguous region of 8 pages. Each page is mapped with +// /// writable permissions, a distinct value is written to each page, and then the value is read +// /// back to verify correctness. Finally, all mappings are removed. +// #[test_case] // Uncomment to run this test. +// async fn test_contiguous_mapping() { +// let mut mapper = KERNEL_MAPPER.lock(); +// +// // Define a contiguous region spanning 8 pages. +// let start_page: Page = Page::containing_address(VirtAddr::new(0x400004000)); +// let num_pages = 8; +// let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; +// +// let mut frames = Vec::new(); +// for i in 0..num_pages { +// let page = Page::from_start_address(start_page.start_address() + i * PAGE_SIZE as u64) +// .expect("Invalid page address"); +// let frame = create_mapping(page, &mut *mapper, Some(flags)); +// frames.push((page, frame)); +// } +// +// // Write and verify distinct values. +// for (i, (page, _)) in frames.iter().enumerate() { +// let ptr = page.start_address().as_mut_ptr::(); +// unsafe { write_volatile(ptr, i as u64) }; +// let val = unsafe { read_volatile(ptr) }; +// assert_eq!(val, i as u64); +// } +// +// // Cleanup: Unmap all pages. +// for (page, _) in frames { +// remove_mapped_frame(page, &mut *mapper); +// } +// } +// +// /// Tests that TLB shootdowns work correctly across cores. +// /// +// /// This test creates a mapping for a given page and writes a value to cache it on the current core. +// /// It then schedules a read of that page on an alternate core (to load it into that core’s TLB cache). +// /// After that, the mapping is updated to a new frame with new contents and the new value is written. +// /// Finally, the test re-schedules a read on the alternate core and verifies that the new value is observed. +// #[test_case] +// async fn test_tlb_shootdowns_cross_core() { +// const AP: u32 = 1; +// const PRIORITY: usize = 3; +// +// // Create mapping and set a value on the current core. +// let page: Page = Page::containing_address(VirtAddr::new(0x500000000)); +// +// { +// let mut mapper = KERNEL_MAPPER.lock(); +// let _ = create_mapping(page, &mut *mapper, None); +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(0xdead); +// } +// } +// +// // Mapping exists now and is cached for the first core. +// +// // Schedule a read on core 1 to load the page into its TLB cache. +// schedule_kernel_on(AP, async move { pre_read(page).await }, PRIORITY); +// +// while PRE_READ.load(Ordering::SeqCst) == 0 { +// core::hint::spin_loop(); +// } +// +// { +// let mut mapper = KERNEL_MAPPER.lock(); +// +// let new_frame = alloc_frame().expect("Could not find a new frame"); +// +// // Update the mapping so that a TLB shootdown is necessary. +// update_mapping( +// page, +// &mut *mapper, +// new_frame, +// Some(PageTableFlags::PRESENT | PageTableFlags::WRITABLE), +// ); +// +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(0x42); +// } +// } +// +// // Schedule another read on core 1 to verify that the new value is visible. +// schedule_kernel_on(AP, async move { post_read(page).await }, PRIORITY); +// +// while POST_READ.load(Ordering::SeqCst) == 0 { +// core::hint::spin_loop(); +// } +// +// assert_eq!(POST_READ.load(Ordering::SeqCst), 0x42); +// +// let mut mapper = KERNEL_MAPPER.lock(); +// remove_mapped_frame(page, &mut *mapper); +// } +// +// // Tests the copy-on-write (COW) mechanism for a mapped page. +// // +// // In a COW scenario, the page is initially mapped as read-only. A write to the page should +// // trigger a fault that results in a new physical frame being allocated and mapped for that page. +// // In this test, we simulate this behavior by: +// // 1. Creating a mapping with read-only permissions. +// // 2. Writing to the page which, triggers a page fault +// // 3. Handling the page fault in our page fault handler +// // 4. Verifying that the new frame is different from the initial one and that the written value is present. +// #[test_case] +// async fn test_copy_on_write() { +// let mut mapper = KERNEL_MAPPER.lock(); +// // Create a dummy PML4 frame. +// // Locate the current process. +// +// const TEST_VALUE: u64 = 0x42; +// +// let page = Page::containing_address(VirtAddr::new(0x400003000)); +// +// let anon_area = Arc::new(VmAreaBackings::new()); +// +// with_current_pcb(|pcb| { +// pcb.mm.with_vma_tree_mutable(|tree| { +// let _vma1 = Mm::insert_vma( +// tree, +// page.start_address().as_u64(), +// page.start_address().as_u64() + PAGE_SIZE as u64, +// anon_area.clone(), +// VmAreaFlags::WRITABLE, +// 0, +// 0, +// ); +// }); +// }); +// +// // Create mapping with read-only permission to simulate a COW mapping. +// let init_frame = create_mapping(page, &mut *mapper, Some(PageTableFlags::PRESENT)); +// +// // Write to the page. +// // Triggers page fault +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(TEST_VALUE); +// } +// +// // Now, translating the page should return the new frame. +// let frame = mapper +// .translate_page(page) +// .expect("Translation after COW failed"); +// +// // The new frame should be different from the original frame. +// assert_ne!(init_frame, frame); +// +// let read_value = unsafe { page.start_address().as_ptr::().read_volatile() }; +// +// assert_eq!(read_value, TEST_VALUE); +// +// // should not trigger a page fault, we should be able to write now +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(0x20); +// } +// +// let new_frame = mapper +// .translate_page(page) +// .expect("Translation after COW failed"); +// +// // We already made this our own, no need to have done COW +// assert_eq!(frame, new_frame); +// +// let read_value2 = unsafe { page.start_address().as_ptr::().read_volatile() }; +// +// assert_eq!(read_value2, 0x20); +// +// remove_mapped_frame(page, &mut *mapper); +// } +// +// /// Tests the copy-on-write (COW) mechanism for a mapped page. +// /// +// /// In a COW scenario, the page is initially mapped as writable, and a full buffer is written +// /// Then, the page is marked read only and the first byte in the buffer is written to. +// /// This should trigger a page fault that does COW, but it should maintain the rest +// /// of the values in the buffer. +// #[test_case] +// async fn test_copy_on_write_full() { +// let mut mapper = KERNEL_MAPPER.lock(); +// // Create a dummy PML4 frame. +// // Locate the current process. +// let pid = get_current_pid(); +// let process = { +// let process_table = PROCESS_TABLE.read(); +// process_table +// .get(&pid) +// .expect("can't find pcb in process table") +// .clone() +// }; +// const TEST_VALUE: u8 = 0x2; +// let page = Page::containing_address(VirtAddr::new(0x400003000)); +// let anon_area = Arc::new(VmAreaBackings::new()); +// +// unsafe { +// (*process.pcb.get()).mm.with_vma_tree_mutable(|tree| { +// let _vma1 = Mm::insert_vma( +// tree, +// page.start_address().as_u64(), +// page.start_address().as_u64() + PAGE_SIZE as u64, +// anon_area.clone(), +// VmAreaFlags::WRITABLE, +// 0, +// 0, +// ); +// }); +// } +// +// // Create mapping with read-only permission to simulate a COW mapping. +// let _ = create_mapping( +// page, +// &mut *mapper, +// Some(PageTableFlags::PRESENT | PageTableFlags::WRITABLE), +// ); +// +// // Write 1s to the entire buffer +// unsafe { +// let buf_ptr = page.start_address().as_mut_ptr::(); +// core::ptr::write_bytes(buf_ptr, 1, PAGE_SIZE); +// } +// +// // Make it so we page fault on write +// update_permissions(page, &mut *mapper, PageTableFlags::PRESENT); +// +// // Write to the page. +// // In a real system, this would trigger a page fault to handle copy-on-write. +// unsafe { +// page.start_address() +// .as_mut_ptr::() +// .write_volatile(TEST_VALUE); +// } +// +// // the cow should not have messed with any data in the buffer besides what +// // we just wrote to +// let read_value = unsafe { page.start_address().as_ptr::().read_volatile() }; +// +// assert_eq!(read_value, TEST_VALUE); +// +// unsafe { +// let buf_ptr = page.start_address().as_ptr::(); +// for i in 1..PAGE_SIZE { +// let val = *buf_ptr.add(i); +// assert_eq!(val, 1, "Byte at offset {} is not 1 (found {})", i, val); +// } +// } +// +// remove_mapped_frame(page, &mut *mapper); +// } +// } diff --git a/kernel/src/processes/loader.rs b/kernel/src/processes/loader.rs index 40ee388f..59ad64fc 100644 --- a/kernel/src/processes/loader.rs +++ b/kernel/src/processes/loader.rs @@ -6,10 +6,10 @@ use crate::{ memory::{ frame_allocator::with_generic_allocator, mm::{Mm, VmAreaBackings, VmAreaFlags}, - paging::{create_mapping, update_permissions}, + paging::{create_mapping, create_mapping_to_frame, update_permissions}, }, }; -use alloc::sync::Arc; +use alloc::{string::String, sync::Arc, vec::Vec}; use core::ptr::{copy_nonoverlapping, write_bytes}; use goblin::{ elf::Elf, @@ -40,6 +40,8 @@ pub fn load_elf( user_mapper: &mut impl Mapper, kernel_mapper: &mut OffsetPageTable<'static>, mm: &mut Mm, + args: Vec, + envs: Vec, ) -> (VirtAddr, u64) { let elf = Elf::parse(elf_bytes).expect("Parsing ELF failed"); for ph in elf.program_headers.iter() { @@ -141,7 +143,17 @@ pub fn load_elf( let stack_end = VirtAddr::new(STACK_START + STACK_SIZE as u64); let _start_page: Page = Page::containing_address(stack_start); let _end_page: Page = Page::containing_address(stack_end); - + let frame = create_mapping( + _end_page, + user_mapper, + Some(PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE | PageTableFlags::WRITABLE), + ); + create_mapping_to_frame( + _end_page, + kernel_mapper, + Some(PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE | PageTableFlags::WRITABLE), + frame, + ); // new anon_vma that corresponds to this stack let anon_vma_stack = Arc::new(VmAreaBackings::new()); @@ -157,5 +169,76 @@ pub fn load_elf( ); }); - (stack_end, elf.header.e_entry) + // 1) Start at top of stack + let mut sp = stack_end; + // 2) Align down to 16 bytes + sp = VirtAddr::new(sp.as_u64() & !0xF); + + // 3) Reserve space for the argument strings themselves, + // writing them at lower addresses, and save their pointers. + let mut arg_ptrs = Vec::with_capacity(args.len()); + for s in args.into_iter().rev() { + let bytes = s.into_bytes(); + let len = bytes.len() + 1; // +1 for '\0' + sp = VirtAddr::new(sp.as_u64() + len as u64); + unsafe { + let dst = sp.as_mut_ptr::(); + core::ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()); + dst.add(bytes.len()).write(0); + } + arg_ptrs.push(sp.as_u64()); + } + arg_ptrs.reverse(); // because we iterated in reverse + + // 4) Same for env strings + let mut env_ptrs = Vec::with_capacity(envs.len()); + for s in envs.into_iter().rev() { + let bytes = s.into_bytes(); + let len = bytes.len() + 1; + sp = VirtAddr::new(sp.as_u64() + len as u64); + unsafe { + let dst = sp.as_mut_ptr::(); + core::ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()); + dst.add(bytes.len()).write(0); + } + let cstr = unsafe { core::ffi::CStr::from_ptr(sp.as_u64() as *const i8) }; + let s = cstr.to_str().unwrap(); + crate::serial_println!("envp: {}", s); + env_ptrs.push(sp.as_u64()); + } + env_ptrs.reverse(); + + for &s in &env_ptrs { + let cstr = unsafe { core::ffi::CStr::from_ptr(s as *const i8) }; + let s = cstr.to_str().unwrap(); + crate::serial_println!("envp: {}", s); + } + + // 5) Align down again before pushing pointer arrays + sp = VirtAddr::new(sp.as_u64() & !0xF); + + // 6) Push envp pointers (NULL-terminated) + for &ptr in env_ptrs.iter().chain(core::iter::once(&0u64)) { + unsafe { + sp.as_mut_ptr::().write(ptr); + } + sp = VirtAddr::new(sp.as_u64() + 8); + } + + // 7) Push argv pointers (NULL-terminated) + for &ptr in arg_ptrs.iter().chain(core::iter::once(&0u64)) { + unsafe { + sp.as_mut_ptr::().write(ptr); + } + sp = VirtAddr::new(sp.as_u64() + 8); + } + + // 8) Finally push argc + let argc = arg_ptrs.len() as u64; + unsafe { + sp.as_mut_ptr::().write(argc); + } + + // Return the new stack pointer and entry point + (sp, elf.header.e_entry) } diff --git a/kernel/src/processes/mod.rs b/kernel/src/processes/mod.rs index 83ab3be9..0f28cd90 100644 --- a/kernel/src/processes/mod.rs +++ b/kernel/src/processes/mod.rs @@ -12,18 +12,27 @@ pub fn init(cpu_id: u32) { #[cfg(test)] mod tests { + use alloc::vec::Vec; + use x86_64::align_up; + use crate::{ - constants::processes::TEST_SIMPLE_PROCESS, + constants::{memory::PAGE_SIZE, processes::TEST_SIMPLE_PROCESS}, events::{ current_running_event, futures::await_on::AwaitProcess, get_runner_time, schedule_process, }, - processes::process::create_process, + filesys::{get_file, FileSystem, OpenFlags, FILESYSTEM}, + processes::{process::create_process, registers::ForkingRegisters}, + serial_println, + syscalls::{ + block::block_on, + memorymap::{sys_mmap, MmapFlags, ProtFlags}, + }, }; #[test_case] async fn test_simple_process() { - let pid = create_process(TEST_SIMPLE_PROCESS); + let pid = create_process(TEST_SIMPLE_PROCESS, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( pid, @@ -33,4 +42,63 @@ mod tests { .await; assert!(waiter.is_ok()); } + + // #[test_case] + async fn test_simple_c_ret() { + let fs = FILESYSTEM.get().unwrap(); + let fd = { + fs.lock() + .await + .open_file( + "/executables/ret", + OpenFlags::O_RDONLY | OpenFlags::O_WRONLY, + ) + .await + .expect("Could not open file") + }; + let file = get_file(fd).unwrap(); + let file_len = { + fs.lock() + .await + .filesystem + .lock() + .get_node(&file.lock().pathname) + .await + .unwrap() + .size() + }; + sys_mmap( + 0x9000, + align_up(file_len, PAGE_SIZE as u64), + ProtFlags::PROT_EXEC.bits(), + MmapFlags::MAP_FILE.bits(), + fd as i64, + 0, + ).await; + + serial_println!("Reading file..."); + + let mut buffer = alloc::vec![0u8; file_len as usize]; + let bytes_read = { + fs.lock() + .await + .read_file(fd, &mut buffer) + .await + .expect("Failed to read file") + }; + + let buf = &buffer[..bytes_read]; + + serial_println!("Bytes read: {:#?}", bytes_read); + + let pid = create_process(buf, Vec::new(), Vec::new()); + schedule_process(pid); + let waiter = AwaitProcess::new( + pid, + get_runner_time(3_000_000_000), + current_running_event().unwrap(), + ) + .await; + assert!(waiter.is_ok()); + } } diff --git a/kernel/src/processes/process.rs b/kernel/src/processes/process.rs index 8875ba15..69474f82 100644 --- a/kernel/src/processes/process.rs +++ b/kernel/src/processes/process.rs @@ -7,8 +7,8 @@ use crate::{ }, debug, events::{ - current_running_event_info, nanosleep_current_process, runner_timestamp, schedule_process, - EventInfo, + current_running_event, current_running_event_info, nanosleep_current_process, + runner_timestamp, yield_now, EventInfo, }, filesys::File, interrupts::{ @@ -24,7 +24,7 @@ use crate::{ processes::{loader::load_elf, registers::Registers}, serial_println, }; -use alloc::{collections::BTreeMap, sync::Arc}; +use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use core::{ arch::naked_asm, borrow::BorrowMut, @@ -58,6 +58,9 @@ pub struct PCB { pub state: ProcessState, pub kernel_rsp: u64, pub kernel_rip: u64, + pub reentry_arg1: u64, + pub reentry_rip: u64, + pub in_kernel: bool, pub next_preemption_time: u64, pub registers: Registers, pub mmap_address: u64, @@ -160,6 +163,9 @@ pub fn create_placeholder_process() -> u32 { state: ProcessState::New, kernel_rsp: 0, kernel_rip: 0, + reentry_arg1: 0, + reentry_rip: 0, + in_kernel: false, registers: Registers { rax: 0, rbx: 0, @@ -182,7 +188,8 @@ pub fn create_placeholder_process() -> u32 { }, mmap_address: START_MMAP_ADDRESS, fd_table: [const { None }; MAX_FILES], - next_fd: Arc::new(Mutex::new(0)), + // 0 and 1 are stdin, stdout + next_fd: Arc::new(Mutex::new(2)), next_preemption_time: 0, mm, namespace: Namespace::new(), @@ -191,8 +198,9 @@ pub fn create_placeholder_process() -> u32 { pid } -pub fn create_process(elf_bytes: &[u8]) -> u32 { +pub fn create_process(elf_bytes: &[u8], args: Vec, envs: Vec) -> u32 { let pid = NEXT_PID.fetch_add(1, Ordering::SeqCst); + with_buddy_frame_allocator(|alloc| { alloc.print_free_frames(); }); @@ -210,6 +218,8 @@ pub fn create_process(elf_bytes: &[u8]) -> u32 { &mut mapper, &mut KERNEL_MAPPER.lock(), mm.borrow_mut(), + args, + envs, ); let process = Arc::new(UnsafePCB::new(PCB { @@ -217,6 +227,9 @@ pub fn create_process(elf_bytes: &[u8]) -> u32 { state: ProcessState::New, kernel_rsp: 0, kernel_rip: 0, + reentry_arg1: 0, + reentry_rip: 0, + in_kernel: false, next_preemption_time: 0, registers: Registers { rax: 0, @@ -240,7 +253,8 @@ pub fn create_process(elf_bytes: &[u8]) -> u32 { }, mmap_address: START_MMAP_ADDRESS, fd_table: [const { None }; MAX_FILES], - next_fd: Arc::new(Mutex::new(0)), + // 0 and 1 are stdin, stdout + next_fd: Arc::new(Mutex::new(2)), mm, namespace: Namespace::new(), })); @@ -333,6 +347,92 @@ use super::registers::ForkingRegisters; /// This process is unsafe because it directly modifies registers #[no_mangle] pub async unsafe fn run_process_ring3(pid: u32) { + resume_process_ring3(pid); + + loop { + let process = { + let process_table = PROCESS_TABLE.read(); + let Some(process) = process_table.get(&pid) else { + serial_println!("Exiting"); + return; + }; + process.clone() + }; + + // Do not lock lowest common denominator + // Once kernel threads are in, will need lock around PCB + // But not TCB + let process = process.pcb.get(); + + let arg1 = (*process).reentry_arg1; + let reentry_rip = (*process).reentry_rip; + let kernel_rsp = &mut (*process).kernel_rsp as *mut u64 as u64; + let in_kernel = (*process).in_kernel; + + if (*process).state == ProcessState::Blocked || (*process).state == ProcessState::Ready { + interrupts::disable(); + + yield_now().await; + if in_kernel { + // Switch back to syscall stack + unsafe { + asm!( + "push rax", + "push rcx", + "push rdx", + "call resume_syscall", + "pop rdx", + "pop rcx", + "pop rax", + in("rdi") arg1, + in("rsi") reentry_rip, + in("rdx") kernel_rsp as *mut u64, + ); + } + } else { + // Came from process (likely timer interrupt preemption) + // No need to check any futures, can simply resume the process + resume_process_ring3(arg1 as u32); + } + } + } +} + +#[unsafe(naked)] +#[no_mangle] +/// # Safety +/// Don't call this unless you are run_process_ring3 +unsafe extern "C" fn resume_syscall(arg1: u64, reentry_rip: u64, kernel_rsp: *mut u64) { + core::arch::naked_asm!( + //save callee-saved registers + " + push rbp + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbx + ", + "swapgs", + "mov r11, rsp", + "mov [rdx], r11", // Save kernel RSP to return + "mov rsp, qword ptr gs:[4]", // Switch to syscall RSP + "mov rdi, rdi", + "push rsi", // Call syscall (using syscall stack) + "cli", // TODO is this safe??? + "ret", + ); +} + +/// # Safety +/// Don't call this unless you are run_process_ring3 +pub unsafe fn resume_process_ring3(pid: u32) { interrupts::disable(); let process = { let process_table = PROCESS_TABLE.read(); @@ -373,7 +473,7 @@ pub async unsafe fn run_process_ring3(pid: u32) { in("rsi") user_ds, in("rdx") user_cs, in("rcx") &(*process).kernel_rsp, - in("r8") &(*process).state + in("r8") &(*process).state, ); } } @@ -438,7 +538,9 @@ unsafe fn call_process( #[unsafe(naked)] #[no_mangle] -unsafe fn return_process() { +/// # Safety +/// Don't call this directly, use function pointers +pub unsafe fn return_process() { naked_asm!( "cli", //disable interrupts //restore callee-saved registers @@ -463,15 +565,21 @@ unsafe fn return_process() { pub fn preempt_process(rsp: u64) { let event: EventInfo = current_running_event_info(); if event.pid == 0 { + x2apic::send_eoi(); return; } // Get PCB from PID let preemption_info = unsafe { let mut process_table = PROCESS_TABLE.write(); - let process = process_table - .get_mut(&event.pid) - .expect("Process not found"); + let Some(process) = process_table.get_mut(&event.pid) else { + debug!( + "Tried to preempt exited process...eid {}", + current_running_event().unwrap().id() + ); + x2apic::send_eoi(); + return; + }; let pcb = process.pcb.get(); @@ -479,6 +587,7 @@ pub fn preempt_process(rsp: u64) { if (*pcb).state != ProcessState::Running || (*pcb).next_preemption_time <= runner_timestamp() { + x2apic::send_eoi(); return; } @@ -507,13 +616,16 @@ pub fn preempt_process(rsp: u64) { (*pcb).state = ProcessState::Ready; + (*pcb).reentry_arg1 = event.pid as u64; + (*pcb).reentry_rip = resume_process_ring3 as usize as u64; + ((*pcb).kernel_rsp, (*pcb).kernel_rip) }; unsafe { - schedule_process(event.pid); + // schedule_process(event.pid); - // Restore kernel RSP + PC -> RIP from where it was stored in run/resume process + // // Restore kernel RSP + PC -> RIP from where it was stored in run/resume process core::arch::asm!( "mov rsp, {0}", "push {1}", @@ -521,65 +633,17 @@ pub fn preempt_process(rsp: u64) { in(reg) preemption_info.1 ); - x2apic::send_eoi(); - - core::arch::asm!("ret"); - } -} - -pub fn block_process(rsp: u64) { - let event: EventInfo = current_running_event_info(); - if event.pid == 0 { - return; - } - - // Get PCB from PID - let preemption_info = unsafe { - let mut process_table = PROCESS_TABLE.write(); - let process = process_table - .get_mut(&event.pid) - .expect("Process not found"); - - let pcb = process.pcb.get(); - - // save registers to the PCB - let stack_ptr: *const u64 = rsp as *const u64; - - (*pcb).registers.rax = *stack_ptr.add(0); - (*pcb).registers.rbx = *stack_ptr.add(1); - (*pcb).registers.rcx = *stack_ptr.add(2); - (*pcb).registers.rdx = *stack_ptr.add(3); - (*pcb).registers.rsi = *stack_ptr.add(4); - (*pcb).registers.rdi = *stack_ptr.add(5); - (*pcb).registers.r8 = *stack_ptr.add(6); - (*pcb).registers.r9 = *stack_ptr.add(7); - (*pcb).registers.r10 = *stack_ptr.add(8); - (*pcb).registers.r11 = *stack_ptr.add(9); - (*pcb).registers.r12 = *stack_ptr.add(10); - (*pcb).registers.r13 = *stack_ptr.add(11); - (*pcb).registers.r14 = *stack_ptr.add(12); - (*pcb).registers.r15 = *stack_ptr.add(13); - (*pcb).registers.rbp = *stack_ptr.add(14); - // saved from interrupt stack frame - (*pcb).registers.rsp = *stack_ptr.add(18); - (*pcb).registers.rip = *stack_ptr.add(15); - (*pcb).registers.rflags = *stack_ptr.add(17); - - (*pcb).state = ProcessState::Blocked; - - ((*pcb).kernel_rsp, (*pcb).kernel_rip) - }; - - unsafe { - // TODO schedule blocked process - // Restore kernel RSP + PC -> RIP from where it was stored in run/resume process - core::arch::asm!( - "mov rsp, {0}", - "push {1}", - in(reg) preemption_info.0, - in(reg) preemption_info.1 - ); + // core::arch::asm!( + // "mov r11, rsp", // Move RSP to R11 + // "mov [rcx], r11", // store RSP (from R11)" + // "mov rsp, {0}", + // "push {1}", + // in(reg) preemption_info.0, + // in(reg) preemption_info.1, + // in("rcx") preemption_info.2, + // out("r11") _ + // ); x2apic::send_eoi(); @@ -705,3 +769,89 @@ pub fn sleep_process_syscall(nanos: u64, reg_vals: &ForkingRegisters) { core::arch::asm!("swapgs", "ret"); } } + +#[cfg(test)] +mod tests { + use crate::{ + constants::processes::TEST_EXIT_CODE, + memory::{mm::Mm, HHDM_OFFSET}, + processes::loader::load_elf, + }; + + use super::*; + use core::slice; + use x86_64::{ + structures::paging::{OffsetPageTable, PageTable, PhysFrame}, + PhysAddr, + }; + + #[test_case] + async fn verify_stack_args_envs() { + // ------ setup exactly as before ------ + let mut user_mapper = unsafe { + let pml4 = create_process_page_table(); + let virt = *HHDM_OFFSET + pml4.start_address().as_u64(); + let ptr = virt.as_mut_ptr::(); + OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) + }; + let args = alloc::vec!["foo".into(), "bar".into()]; + let envs = alloc::vec!["X=1".into(), "Y=two".into()]; + let pml4_frame = PhysFrame::containing_address(PhysAddr::new(0x1000)); + let mut mm = Mm::new(pml4_frame); + + // call loader: `sp` is the address of the u64 slot containing `argc` + let (sp, _entry) = load_elf( + TEST_EXIT_CODE, + &mut user_mapper, + &mut KERNEL_MAPPER.lock(), + &mut mm, + args.clone(), + envs.clone(), + ); + + // total u64 slots we pushed: envs + NULL, args + NULL, argc + let nen = envs.len() as u64; + let nar = args.len() as u64; + + // ---- 1) verify argc ---- + // `sp` points at argc, so just read it + let got_argc = unsafe { (sp.as_u64() as *const u64).read() }; + assert_eq!(got_argc, nar, "argc mismatch"); + + // 2) verify argv + let argv0_ptr = (sp.as_u64() - 8 * (nar + 1)) as *const u64; + (0..(nar as usize)).for_each(|i| { + // read argv[i] + let str_addr = unsafe { argv0_ptr.add(i).read() as *const u8 }; + // walk until NUL + let mut len = 0; + while unsafe { *str_addr.add(len) } != 0 { + len += 1; + } + let got = core::str::from_utf8(unsafe { slice::from_raw_parts(str_addr, len) }) + .expect("Invalid UTF-8 in argv"); + serial_println!("GOT: {:#?}", got); + assert_eq!(got, &args[i], "argv[{}] mismatch", i); + }); + + // ---- 3) verify envp pointers & strings ---- + // envp[] sits *below* argv array + its NULL terminator: + // offset = sp - 8 * (nar + 1) - 8 * (nen + 1) + let envp0_ptr = ( + sp.as_u64() + - 8 * (nar + 1) // skip argv + NULL + - 8 * (nen + 1) + // skip envp + NULL + ) as *const u64; + (0..(nen as usize)).for_each(|i| { + let str_addr = unsafe { envp0_ptr.add(i).read() as *const u8 }; + let mut len = 0; + while unsafe { *str_addr.add(len) } != 0 { + len += 1; + } + let got = core::str::from_utf8(unsafe { slice::from_raw_parts(str_addr, len) }) + .expect("Invalid UTF-8 in envp"); + assert_eq!(got, &envs[i], "envp[{}] mismatch", i); + }); + } +} diff --git a/kernel/src/processes/registers.rs b/kernel/src/processes/registers.rs index 0fcf062e..677fa490 100644 --- a/kernel/src/processes/registers.rs +++ b/kernel/src/processes/registers.rs @@ -47,6 +47,7 @@ pub struct Registers { pub rflags: u64, } #[derive(Debug, Default, Clone, PartialEq, Eq)] +#[repr(C)] pub struct ForkingRegisters { pub rsp: u64, pub rbx: u64, diff --git a/kernel/src/processes/test_binaries/print_exit.asm b/kernel/src/processes/test_binaries/print_exit.asm deleted file mode 100644 index f82b5555..00000000 --- a/kernel/src/processes/test_binaries/print_exit.asm +++ /dev/null @@ -1,17 +0,0 @@ -section .data - buffer db "Hello from custom syscall!", 0 - -section .text -global _start - -_start: - ; Set up registers for the custom syscall - mov rax, 3 ; Custom syscall number 3 - mov rdi, buffer ; First argument: pointer to our string - - syscall - - ; Exit the program using the Linux exit syscall (number 1) - mov rax, 60 ; syscall: exit - xor rdi, rdi ; exit code 0 - syscall diff --git a/kernel/src/processes/test_binaries/print_exit b/kernel/src/processes/test_binaries/sleep similarity index 89% rename from kernel/src/processes/test_binaries/print_exit rename to kernel/src/processes/test_binaries/sleep index 3d7c57f1..93911516 100755 Binary files a/kernel/src/processes/test_binaries/print_exit and b/kernel/src/processes/test_binaries/sleep differ diff --git a/kernel/src/processes/test_binaries/sleep.asm b/kernel/src/processes/test_binaries/sleep.asm new file mode 100644 index 00000000..c681a554 --- /dev/null +++ b/kernel/src/processes/test_binaries/sleep.asm @@ -0,0 +1,19 @@ +section .data + buffer db "Hello, syscall sleep!", 0 + +section .text + global _start + +_start: + mov rdi, buffer + mov rax, 1003 + mov rdx, 35 + mov r8, 60 + syscall ; print + + mov rdi, 5000000000 + mov rax, rdx + syscall ; nanosleep + + mov rax, r8 + syscall ; exit diff --git a/kernel/src/processes/test_binaries/test_print_exit b/kernel/src/processes/test_binaries/test_print_exit index 90aae583..55b8d304 100755 Binary files a/kernel/src/processes/test_binaries/test_print_exit and b/kernel/src/processes/test_binaries/test_print_exit differ diff --git a/kernel/src/processes/test_binaries/test_print_exit.asm b/kernel/src/processes/test_binaries/test_print_exit.asm index 9fa2d297..798efeba 100644 --- a/kernel/src/processes/test_binaries/test_print_exit.asm +++ b/kernel/src/processes/test_binaries/test_print_exit.asm @@ -6,11 +6,18 @@ section .text _start: ; Set up registers for the custom syscall - mov rax, 3 ; Custom syscall number 3 + mov rax, 1003 ; Custom syscall number 1003 (print) mov rdi, buffer ; First argument: pointer to our string syscall + mov rbx, 0xFFFFFF + +_loop: + sub rbx, 1 + cmp rbx, 0 + jg _loop + ; Exit the program using the Linux exit syscall (number 1) mov rax, 60 ; syscall: exit xor rdi, rdi ; exit code 0 diff --git a/kernel/src/shell/mod.rs b/kernel/src/shell/mod.rs new file mode 100644 index 00000000..327cf1b0 --- /dev/null +++ b/kernel/src/shell/mod.rs @@ -0,0 +1 @@ +pub mod shell; diff --git a/kernel/src/shell/shell.rs b/kernel/src/shell/shell.rs new file mode 100644 index 00000000..b9b4482a --- /dev/null +++ b/kernel/src/shell/shell.rs @@ -0,0 +1,251 @@ +use alloc::{ + fmt::format, + string::{String, ToString}, + vec::Vec, +}; + +use crate::{ + devices::ps2_dev::keyboard, + events::schedule_kernel, + processes::registers::ForkingRegisters, + serial_println, + syscalls::{ + block::{block_on, spin_on}, + syscall_handlers::{sys_exec, sys_read, sys_write}, + }, +}; + +pub struct Shell { + buffer: [u8; 256], + position: usize, + env: Vec, +} +impl Shell { + pub fn new() -> Self { + Self { + buffer: [0; 256], + position: 0, + env: Vec::new(), + } + } + pub fn run(self) { + serial_println!("SHELL RUNNING"); + self.print_prompt(); + let mut i = 0; + + schedule_kernel( + async move { + let mut shell = self; + loop { + let c = shell.read_char(); + match c { + b'\n' | b'\r' => { + shell.execute_command().await; + keyboard::flush_buffer().await; + shell.print_prompt(); + serial_println!("ENVS: {:#?}", shell.env); + // TODO: Until the heap allocation error is fixed arbitrary number of + // commands allowed to run + i += 1; + if i == 5 { + return; + } + } + 0x08 => shell.handle_backspace(), + _ if c.is_ascii_graphic() || c == b' ' => shell.handle_char(c), + _ => {} + } + // yield_now().await + } + }, + 3, + ); + } + + fn read_char(&mut self) -> u8 { + let mut c: u8 = 0; + unsafe { + spin_on( + sys_read(0, &mut c as *mut u8, 1), + ) + }; + match c { + b'\n' => { + // Enter handled separately + } + 0x08 => { + // Backspace handled separately + } + _ => { + // Only print printable ASCII characters + if c.is_ascii_graphic() || c == b' ' { + self.print(&format(format_args!("{}", c as char))); + } + } + } + c + } + + fn print(&self, s: &str) { + unsafe { + spin_on( + sys_write(1, s.as_ptr() as *mut u8, s.len()) + ) + }; + } + + fn handle_char(&mut self, c: u8) { + if self.position < self.buffer.len() - 1 { + self.buffer[self.position] = c; + self.position += 1; + } + } + + fn handle_backspace(&mut self) { + if self.position > 0 { + self.position -= 1; + self.print("\x08 \x08"); + } + } + + async fn execute_command(&mut self) { + let cmd_owned = { + let slice = &self.buffer[..self.position]; + String::from_utf8_lossy(slice).to_string() + }; + self.print("\n"); + self.process_command(&cmd_owned); + self.position = 0; + } + + fn process_command(&mut self, cmd: &str) { + let trimmed = cmd.trim(); + match trimmed { + "" => {} + + "help" => self.print("Available: help, echo, clear, export, [/cmd]\n"), + + "clear" => self.print("\x1B[2J\x1B[H"), + + t if t.starts_with("echo ") => { + let raw = t.strip_prefix("echo ").unwrap(); + // simple var‐expansion: $VAR or ${VAR} + let mut out = String::new(); + let mut chars = raw.chars().peekable(); + while let Some(c) = chars.next() { + // At this point we know that we're printing out an environment variable + if c == '$' { + // detect ${VAR} vs $VAR + let name = if chars.peek() == Some(&'{') { + chars.next(); // skip '{' + let mut nm = String::new(); + while let Some(&nx) = chars.peek() { + if nx == '}' { + chars.next(); + break; + } + nm.push(nx); + chars.next(); + } + nm + } else { + let mut nm = String::new(); + while let Some(&next) = chars.peek() { + if !next.is_ascii_alphanumeric() && next != '_' { + break; + } + nm.push(next); + chars.next(); + } + nm + }; + // lookup NAME in self.env + let val = self + .env + .iter() + .find_map(|kv| { + let mut sp = kv.splitn(2, '='); + if sp.next()? == name { + sp.next().map(|v| v.to_string()) + } else { + None + } + }) + .unwrap_or_default(); + out.push_str(&val); + } else { + out.push(c); + } + } + self.print(&format(format_args!("{out}\n"))); + } + + t if t.starts_with("export ") => { + // export KEY=VAL + if let Some(rest) = t.strip_prefix("export ") { + if let Some((k, v)) = rest.split_once('=') { + let pair = format(format_args!("{k}={v}")); + self.env.push(pair); + } + } + } + + t if t.starts_with('/') => { + // build argv[] + const MAX_ARGS: usize = 16; + let mut argv: [*mut u8; MAX_ARGS + 1] = [core::ptr::null_mut(); MAX_ARGS + 1]; + let mut argc = 0; + let mut start = 0; + + // split buffer on spaces + for i in 0..=self.position { + if i == self.position || self.buffer[i] == b' ' { + self.buffer[i] = 0; + if argc < MAX_ARGS { + argv[argc] = unsafe { self.buffer.as_mut_ptr().add(start) }; + argc += 1; + } + start = i + 1; + } + } + // end + argv[argc] = core::ptr::null_mut(); + + // build envp[] + let mut envp: Vec<*mut u8> = self + .env + .iter_mut() + .map(|s| { + s.push('\0'); + s.as_mut_ptr() + }) + .collect(); + // end + envp.push(core::ptr::null_mut()); + serial_println!("EXECUTING"); + unsafe { + sys_exec(argv[0], argv.as_mut_ptr(), envp.as_mut_ptr()); + } + } + + _ => self.print("Unknown command\n"), + } + } + + fn print_prompt(&self) { + self.print("> "); + } +} + +impl Default for Shell { + fn default() -> Self { + Self::new() + } +} + +/// # Safety +/// TODO +pub unsafe fn init() { + let shell = Shell::new(); + shell.run(); +} diff --git a/kernel/src/syscalls/block.rs b/kernel/src/syscalls/block.rs new file mode 100644 index 00000000..1ab5fecc --- /dev/null +++ b/kernel/src/syscalls/block.rs @@ -0,0 +1,201 @@ +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +use alloc::boxed::Box; + +use crate::{ + events::current_running_event_info, + processes::{ + process::{ProcessState, PROCESS_TABLE}, + registers::ForkingRegisters, + }, +}; + +/// Helper function for sys_wait, not sure if necessary +/// TODO make this into a real block (bring back pawait) +pub fn spin_on(mut future: F) -> F::Output { + let waker = unsafe { Waker::from_raw(anoop_raw_waker()) }; + let mut cx = Context::from_waker(&waker); + // Safety: we’re not moving the future while polling. + let mut future = unsafe { Pin::new_unchecked(&mut future) }; + loop { + if let Poll::Ready(val) = future.as_mut().poll(&mut cx) { + return val; + } + } +} + +pub(crate) fn block_on>(future: F, reg_vals: &ForkingRegisters) -> u64 { + // Move future to heap + let fut = Box::from(future); + + unsafe { block_on_helper(Box::into_raw(fut), reg_vals) } +} + +fn anoop_raw_waker() -> RawWaker { + fn clone(_: *const ()) -> RawWaker { + anoop_raw_waker() + } + fn wake(_: *const ()) {} + fn wake_by_ref(_: *const ()) {} + fn drop(_: *const ()) {} + let vtable = &RawWakerVTable::new(clone, wake, wake_by_ref, drop); + RawWaker::new(core::ptr::null(), vtable) +} + +unsafe fn block_on_helper + ?Sized>( + fut_ptr: *mut F, + reg_vals: *const ForkingRegisters, +) -> u64 { + // TODO option to "spin poll" with max iterations, to prevent exessive yielding + let waker = unsafe { Waker::from_raw(anoop_raw_waker()) }; + let mut cx = Context::from_waker(&waker); + + let mut future = unsafe { Pin::new_unchecked(&mut *fut_ptr) }; + + // TODO remove + if let Poll::Ready(val) = future.as_mut().poll(&mut cx) { + // We haven't yet yielded, so act like normal + drop(Box::from_raw(fut_ptr)); + return val; + } + + let preemption_info: (u64, u64) = { + let pid = current_running_event_info().pid; + let mut process_table = PROCESS_TABLE.write(); + let process = process_table.get_mut(&pid).expect("Process not found"); + + let pcb = process.pcb.get(); + + (*pcb).state = ProcessState::Ready; + // TODO could also set state to Blocked (and invoke block_process??) + // For now just poll from scheduler (and thus allow other things to poll in-between) + + (*pcb).registers.rbp = (*reg_vals).rbp; + (*pcb).registers.r15 = (*reg_vals).r15; + (*pcb).registers.r14 = (*reg_vals).r14; + (*pcb).registers.r13 = (*reg_vals).r13; + (*pcb).registers.r12 = (*reg_vals).r12; + (*pcb).registers.r11 = (*reg_vals).r11; + (*pcb).registers.r10 = (*reg_vals).r10; + (*pcb).registers.r9 = (*reg_vals).r9; + (*pcb).registers.r8 = (*reg_vals).r8; + (*pcb).registers.rdi = (*reg_vals).rdi; + (*pcb).registers.rsi = (*reg_vals).rsi; + (*pcb).registers.rdx = (*reg_vals).rdx; + (*pcb).registers.rcx = (*reg_vals).rcx; + (*pcb).registers.rbx = (*reg_vals).rbx; + + (*pcb).registers.rsp = (*reg_vals).rsp; + + (*pcb).reentry_arg1 = fut_ptr as *const () as u64; + (*pcb).reentry_rip = retry_block_on_helper:: as usize as u64; + (*pcb).in_kernel = true; + + ((*pcb).kernel_rsp, (*pcb).kernel_rip) + }; + + // Restore kernel RSP + PC -> RIP from where it was stored in run/resume process + core::arch::asm!( + "mov rsp, {0}", + "push {1}", + "swapgs", + "ret", + in(reg) preemption_info.0, + in(reg) preemption_info.1, + ); + + unreachable!("If future is not ready, should yield back to scheduler") +} + +/// # Safety +/// Only public for access from processes; should not be called directly +pub unsafe extern "C" fn retry_block_on_helper + ?Sized>( + fut_ptr: *mut F, +) -> ! { + // TODO option to "spin poll" with max iterations, to prevent exessive yielding + let waker = unsafe { Waker::from_raw(anoop_raw_waker()) }; + let mut cx = Context::from_waker(&waker); + + let future = unsafe { Pin::new_unchecked(&mut *fut_ptr) }; + + // TODO remove + if let Poll::Ready(val) = future.poll(&mut cx) { + let pid = current_running_event_info().pid; + let regs = { + let mut process_table = PROCESS_TABLE.write(); + let process = process_table.get_mut(&pid).expect("Process not found"); + + let pcb = process.pcb.get(); + (*pcb).in_kernel = false; + (*pcb).state = ProcessState::Running; + + &(*pcb).registers + }; + + drop(Box::from_raw(fut_ptr)); + + // We've yielded before, so stack is unreliable + core::arch::asm!( + // Restore registers directly from PCB + "mov rbx, [rcx+8]", + "mov rdx, [rcx+24]", + "mov rsi, [rcx+32]", + "mov rdi, [rcx+40]", + "mov r8, [rcx+48]", + "mov r9, [rcx+56]", + "mov r10, [rcx+64]", + "mov r11, [rcx+72]", + "mov r12, [rcx+80]", + "mov r13, [rcx+88]", + "mov r14, [rcx+96]", + "mov r15, [rcx+104]", + + "mov rsp, [rcx+120]", + "mov rcx, [rcx+16]", + + // Swap GS back. + "swapgs", + // Return to user mode. sysretq will use RCX (which contains the user RIP) + // and R11 (which holds user RFLAGS). + "sti", + "sysretq", + in("rax") val, + in("rcx") regs + ); + + unreachable!("If future is ready, should return to process") + } + + let preemption_info: (u64, u64) = { + let pid = current_running_event_info().pid; + let mut process_table = PROCESS_TABLE.write(); + let process = process_table.get_mut(&pid).expect("Process not found"); + + let pcb = process.pcb.get(); + + (*pcb).state = ProcessState::Ready; + // TODO could also set state to Blocked (and invoke block_process??) + // For now just poll from scheduler (and thus allow other things to poll in-between) + + // No need to save user registers; must have done so already last time we yielded + + // TODO reentry_rip + ((*pcb).kernel_rsp, (*pcb).kernel_rip) + }; + + // Restore kernel RSP + PC -> RIP from where it was stored in run/resume process + core::arch::asm!( + "mov rsp, {0}", + "push {1}", + "swapgs", + "ret", + in(reg) preemption_info.0, + in(reg) preemption_info.1, + ); + + unreachable!("If future is not ready, should yield back to scheduler") +} diff --git a/kernel/src/syscalls/fork.rs b/kernel/src/syscalls/fork.rs index e3399ef6..1586079f 100644 --- a/kernel/src/syscalls/fork.rs +++ b/kernel/src/syscalls/fork.rs @@ -170,144 +170,145 @@ fn duplicate_page_table_recursive(parent_frame: PhysFrame, level: u8) -> PhysFra child_frame } -#[cfg(test)] -mod tests { - use x86_64::structures::paging::{ - OffsetPageTable, PageTable, PageTableFlags, PhysFrame, Size4KiB, - }; - - use crate::{ - constants::processes::FORK_SIMPLE, - events::{ - current_running_event, futures::await_on::AwaitProcess, get_runner_time, - schedule_process, - }, - memory::HHDM_OFFSET, - processes::{process::create_process, registers::ForkingRegisters}, - syscalls::syscall_handlers::{EXIT_CODES, PML4_FRAMES, REGISTER_VALUES}, - }; - - fn verify_page_table_walk(parent_pml4: PhysFrame, child_pml4: PhysFrame) { - let parent_mapper = unsafe { - let virt = *HHDM_OFFSET + parent_pml4.start_address().as_u64(); - let ptr = virt.as_mut_ptr::(); - OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) - }; - let child_mapper = unsafe { - let virt = *HHDM_OFFSET + child_pml4.start_address().as_u64(); - let ptr = virt.as_mut_ptr::(); - OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) - }; - - for i in 0..256 { - let parent_entry = &parent_mapper.level_4_table()[i]; - let child_entry = &child_mapper.level_4_table()[i]; - if parent_entry.is_unused() { - assert!(child_entry.is_unused()); - continue; - } else { - assert_eq!(parent_entry.flags(), child_entry.flags()); - let parent_pdpt_frame = PhysFrame::containing_address(parent_entry.addr()); - recursive_walk(parent_pdpt_frame, 3); - } - } - } - - fn recursive_walk(parent_frame: PhysFrame, level: u8) { - let parent_virt = HHDM_OFFSET.as_u64() + parent_frame.start_address().as_u64(); - - let parent_table = unsafe { &mut *(parent_virt as *mut PageTable) }; - let child_table = unsafe { &mut *(parent_virt as *mut PageTable) }; - - for i in 0..512 { - let parent_entry = &parent_table[i]; - let child_entry = &child_table[i]; - if parent_entry.is_unused() { - assert!(child_entry.is_unused()); - continue; - } - - // from the parent and child tables, ensure each entry is the same - assert_eq!(parent_entry.flags(), child_entry.flags()); - if level == 1 { - // This logic is not correct and only works if you don't update the Writable flag during COW duplicate - if parent_entry.flags().contains(PageTableFlags::WRITABLE) { - assert!(parent_entry.flags().contains(PageTableFlags::BIT_9)); - } - assert_eq!(parent_entry.addr(), child_entry.addr()); - assert_eq!( - parent_entry.frame().expect("Could not find frame."), - child_entry.frame().expect("Could not find frame.") - ); - } - if level > 1 { - let parent_frame: PhysFrame = PhysFrame::containing_address(parent_entry.addr()); - recursive_walk(parent_frame, level - 1); - } - } - } - - #[test_case] - async fn test_simple_fork() { - let parent_pid = create_process(FORK_SIMPLE); - schedule_process(parent_pid); - let _waiter = AwaitProcess::new( - parent_pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - let child_pid = parent_pid + 1; - - let _waiter = AwaitProcess::new( - parent_pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - let _waiter = AwaitProcess::new( - child_pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - let exit_codes = EXIT_CODES.lock(); - let parent_exit_code = exit_codes - .get(&parent_pid) - .expect("Could not find parent pid."); - let child_exit_code = exit_codes - .get(&child_pid) - .expect("Could not find child pid."); - - let registers = REGISTER_VALUES.lock(); - - let (parent_regs_ptr, child_regs_ptr) = { - let parent = registers - .get(&parent_pid) - .expect("Could not find parent pid.") - as *const ForkingRegisters; - let child = registers - .get(&child_pid) - .expect("Could not find child pid.") - as *const ForkingRegisters; - (parent, child) - }; - - let parent_regs: &mut ForkingRegisters = unsafe { &mut *(parent_regs_ptr as *mut _) }; - let child_regs: &mut ForkingRegisters = unsafe { &mut *(child_regs_ptr as *mut _) }; - - let frames = PML4_FRAMES.lock(); - let parent_pml4 = frames.get(&parent_pid).expect("Could not find parent pid."); - let child_pml4 = frames.get(&child_pid).expect("Could not find child pid."); - - assert_eq!(parent_exit_code, child_exit_code); - assert_eq!(child_pid as u64, parent_regs.r12); - child_regs.r12 = child_pid as u64; - assert_eq!(parent_regs, child_regs); - - // check that the pml4 frame is set correctly - verify_page_table_walk(*parent_pml4, *child_pml4); - } -} +// #[cfg(test)] +// mod tests { +// use alloc::vec::Vec; +// use x86_64::structures::paging::{ +// OffsetPageTable, PageTable, PageTableFlags, PhysFrame, Size4KiB, +// }; +// +// use crate::{ +// constants::processes::FORK_SIMPLE, +// events::{ +// current_running_event, futures::await_on::AwaitProcess, get_runner_time, +// schedule_process, +// }, +// memory::HHDM_OFFSET, +// processes::{process::create_process, registers::ForkingRegisters}, +// syscalls::syscall_handlers::{EXIT_CODES, PML4_FRAMES, REGISTER_VALUES}, +// }; +// +// fn verify_page_table_walk(parent_pml4: PhysFrame, child_pml4: PhysFrame) { +// let parent_mapper = unsafe { +// let virt = *HHDM_OFFSET + parent_pml4.start_address().as_u64(); +// let ptr = virt.as_mut_ptr::(); +// OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) +// }; +// let child_mapper = unsafe { +// let virt = *HHDM_OFFSET + child_pml4.start_address().as_u64(); +// let ptr = virt.as_mut_ptr::(); +// OffsetPageTable::new(&mut *ptr, *HHDM_OFFSET) +// }; +// +// for i in 0..256 { +// let parent_entry = &parent_mapper.level_4_table()[i]; +// let child_entry = &child_mapper.level_4_table()[i]; +// if parent_entry.is_unused() { +// assert!(child_entry.is_unused()); +// continue; +// } else { +// assert_eq!(parent_entry.flags(), child_entry.flags()); +// let parent_pdpt_frame = PhysFrame::containing_address(parent_entry.addr()); +// recursive_walk(parent_pdpt_frame, 3); +// } +// } +// } +// +// fn recursive_walk(parent_frame: PhysFrame, level: u8) { +// let parent_virt = HHDM_OFFSET.as_u64() + parent_frame.start_address().as_u64(); +// +// let parent_table = unsafe { &mut *(parent_virt as *mut PageTable) }; +// let child_table = unsafe { &mut *(parent_virt as *mut PageTable) }; +// +// for i in 0..512 { +// let parent_entry = &parent_table[i]; +// let child_entry = &child_table[i]; +// if parent_entry.is_unused() { +// assert!(child_entry.is_unused()); +// continue; +// } +// +// // from the parent and child tables, ensure each entry is the same +// assert_eq!(parent_entry.flags(), child_entry.flags()); +// if level == 1 { +// // This logic is not correct and only works if you don't update the Writable flag during COW duplicate +// if parent_entry.flags().contains(PageTableFlags::WRITABLE) { +// assert!(parent_entry.flags().contains(PageTableFlags::BIT_9)); +// } +// assert_eq!(parent_entry.addr(), child_entry.addr()); +// assert_eq!( +// parent_entry.frame().expect("Could not find frame."), +// child_entry.frame().expect("Could not find frame.") +// ); +// } +// if level > 1 { +// let parent_frame: PhysFrame = PhysFrame::containing_address(parent_entry.addr()); +// recursive_walk(parent_frame, level - 1); +// } +// } +// } +// +// #[test_case] +// async fn test_simple_fork() { +// let parent_pid = create_process(FORK_SIMPLE, Vec::new(), Vec::new()); +// schedule_process(parent_pid); +// let _waiter = AwaitProcess::new( +// parent_pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// let child_pid = parent_pid + 1; +// +// let _waiter = AwaitProcess::new( +// parent_pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// let _waiter = AwaitProcess::new( +// child_pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// let exit_codes = EXIT_CODES.lock(); +// let parent_exit_code = exit_codes +// .get(&parent_pid) +// .expect("Could not find parent pid."); +// let child_exit_code = exit_codes +// .get(&child_pid) +// .expect("Could not find child pid."); +// +// let registers = REGISTER_VALUES.lock(); +// +// let (parent_regs_ptr, child_regs_ptr) = { +// let parent = registers +// .get(&parent_pid) +// .expect("Could not find parent pid.") +// as *const ForkingRegisters; +// let child = registers +// .get(&child_pid) +// .expect("Could not find child pid.") +// as *const ForkingRegisters; +// (parent, child) +// }; +// +// let parent_regs: &mut ForkingRegisters = unsafe { &mut *(parent_regs_ptr as *mut _) }; +// let child_regs: &mut ForkingRegisters = unsafe { &mut *(child_regs_ptr as *mut _) }; +// +// let frames = PML4_FRAMES.lock(); +// let parent_pml4 = frames.get(&parent_pid).expect("Could not find parent pid."); +// let child_pml4 = frames.get(&child_pid).expect("Could not find child pid."); +// +// assert_eq!(parent_exit_code, child_exit_code); +// assert_eq!(child_pid as u64, parent_regs.r12); +// child_regs.r12 = child_pid as u64; +// assert_eq!(parent_regs, child_regs); +// +// // check that the pml4 frame is set correctly +// verify_page_table_walk(*parent_pml4, *child_pml4); +// } +// } diff --git a/kernel/src/syscalls/memorymap.rs b/kernel/src/syscalls/memorymap.rs index 607de3a1..05cda2a8 100644 --- a/kernel/src/syscalls/memorymap.rs +++ b/kernel/src/syscalls/memorymap.rs @@ -144,7 +144,7 @@ pub fn mmap_prot_to_vma_flags(prot: u64, mmap_flags: MmapFlags) -> VmAreaFlags { vma_flags } -pub fn sys_mmap(_addr: u64, len: u64, prot: u64, flags: u64, fd: i64, offset: u64) -> u64 { +pub async fn sys_mmap(_addr: u64, len: u64, prot: u64, flags: u64, fd: i64, offset: u64) -> u64 { // Basic sanity check. if len == 0 { panic!("mmap called with zero length"); @@ -439,76 +439,78 @@ pub fn sys_munmap(addr: u64, len: u64) -> u64 { } // TODO: Mmap tests -#[cfg(test)] -mod tests { - use crate::{ - constants::processes::{ - TEST_MMAP_ANON_SHARED, TEST_MMAP_CHILD_WRITES, TEST_MPROTECT_CHILD_WRITES, - }, - events::schedule_process, - processes::process::create_process, - syscalls::syscall_handlers::REGISTER_VALUES, - }; - - use crate::events::{current_running_event, futures::await_on::AwaitProcess, get_runner_time}; - - #[test_case] - async fn mmap_anonymous_shared() { - let pid = create_process(TEST_MMAP_ANON_SHARED); - schedule_process(pid); - - let waiter = AwaitProcess::new( - pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - assert!(waiter.is_ok()); - let reg_values = REGISTER_VALUES.lock(); - let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); - assert_eq!(reg_vals_on_exit.r8, 'A' as u64); - assert_eq!(reg_vals_on_exit.r9, 'B' as u64); - assert_eq!(reg_vals_on_exit.r10, 'C' as u64); - } - - #[test_case] - async fn mmap_anonymous_child_writes_first() { - let pid = create_process(TEST_MMAP_CHILD_WRITES); - schedule_process(pid); - - let waiter = AwaitProcess::new( - pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - assert!(waiter.is_ok()); - let reg_values = REGISTER_VALUES.lock(); - let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); - assert_eq!(reg_vals_on_exit.r8, 'X' as u64); - assert_eq!(reg_vals_on_exit.r9, 'Y' as u64); - assert_eq!(reg_vals_on_exit.r10, 'Z' as u64); - } - - #[test_case] - async fn mprotect_child_writes() { - let pid = create_process(TEST_MPROTECT_CHILD_WRITES); - schedule_process(pid); - - let waiter = AwaitProcess::new( - pid, - get_runner_time(3_000_000_000), - current_running_event().unwrap(), - ) - .await; - - assert!(waiter.is_ok()); - let reg_values = REGISTER_VALUES.lock(); - let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); - assert_eq!(reg_vals_on_exit.r8, 'X' as u64); - assert_eq!(reg_vals_on_exit.r9, 'Y' as u64); - assert_eq!(reg_vals_on_exit.r10, 'Z' as u64); - } -} +// #[cfg(test)] +// mod tests { +// use alloc::vec::Vec; +// +// use crate::{ +// constants::processes::{ +// TEST_MMAP_ANON_SHARED, TEST_MMAP_CHILD_WRITES, TEST_MPROTECT_CHILD_WRITES, +// }, +// events::schedule_process, +// processes::process::create_process, +// syscalls::syscall_handlers::REGISTER_VALUES, +// }; +// +// use crate::events::{current_running_event, futures::await_on::AwaitProcess, get_runner_time}; +// +// #[test_case] +// async fn mmap_anonymous_shared() { +// let pid = create_process(TEST_MMAP_ANON_SHARED, Vec::new(), Vec::new()); +// schedule_process(pid); +// +// let waiter = AwaitProcess::new( +// pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// assert!(waiter.is_ok()); +// let reg_values = REGISTER_VALUES.lock(); +// let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); +// assert_eq!(reg_vals_on_exit.r8, 'A' as u64); +// assert_eq!(reg_vals_on_exit.r9, 'B' as u64); +// assert_eq!(reg_vals_on_exit.r10, 'C' as u64); +// } +// +// #[test_case] +// async fn mmap_anonymous_child_writes_first() { +// let pid = create_process(TEST_MMAP_CHILD_WRITES, Vec::new(), Vec::new()); +// schedule_process(pid); +// +// let waiter = AwaitProcess::new( +// pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// assert!(waiter.is_ok()); +// let reg_values = REGISTER_VALUES.lock(); +// let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); +// assert_eq!(reg_vals_on_exit.r8, 'X' as u64); +// assert_eq!(reg_vals_on_exit.r9, 'Y' as u64); +// assert_eq!(reg_vals_on_exit.r10, 'Z' as u64); +// } +// +// #[test_case] +// async fn mprotect_child_writes() { +// let pid = create_process(TEST_MPROTECT_CHILD_WRITES, Vec::new(), Vec::new()); +// schedule_process(pid); +// +// let waiter = AwaitProcess::new( +// pid, +// get_runner_time(3_000_000_000), +// current_running_event().unwrap(), +// ) +// .await; +// +// assert!(waiter.is_ok()); +// let reg_values = REGISTER_VALUES.lock(); +// let reg_vals_on_exit = reg_values.get(&pid).expect("No process found."); +// assert_eq!(reg_vals_on_exit.r8, 'X' as u64); +// assert_eq!(reg_vals_on_exit.r9, 'Y' as u64); +// assert_eq!(reg_vals_on_exit.r10, 'Z' as u64); +// } +// } diff --git a/kernel/src/syscalls/mod.rs b/kernel/src/syscalls/mod.rs index c22f6dcb..cc724dbf 100644 --- a/kernel/src/syscalls/mod.rs +++ b/kernel/src/syscalls/mod.rs @@ -1,9 +1,11 @@ +pub mod block; pub mod fork; pub mod memorymap; pub mod syscall_handlers; #[cfg(test)] mod tests { + use alloc::vec::Vec; use syscall_handlers::EXIT_CODES; use crate::{ @@ -20,7 +22,7 @@ mod tests { /// The binary exits with code 0 immediately #[test_case] async fn test_exit_code() { - let pid = create_process(TEST_EXIT_CODE); + let pid = create_process(TEST_EXIT_CODE, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( @@ -39,7 +41,7 @@ mod tests { /// content is correct #[test_case] async fn test_print_exit() { - let pid = create_process(TEST_PRINT_EXIT); + let pid = create_process(TEST_PRINT_EXIT, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( @@ -61,7 +63,7 @@ mod tests { /// from waiting #[test_case] async fn test_fork_wait() { - let pid = create_process(TEST_WAIT); + let pid = create_process(TEST_WAIT, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( @@ -83,7 +85,7 @@ mod tests { /// Currently, requires manual verification of buffer #[test_case] async fn test_fork_cow() { - let pid = create_process(TEST_FORK_COW); + let pid = create_process(TEST_FORK_COW, Vec::new(), Vec::new()); schedule_process(pid); let waiter = AwaitProcess::new( diff --git a/kernel/src/syscalls/syscall_handlers.rs b/kernel/src/syscalls/syscall_handlers.rs index 221121ba..2d1a0cd7 100644 --- a/kernel/src/syscalls/syscall_handlers.rs +++ b/kernel/src/syscalls/syscall_handlers.rs @@ -1,30 +1,41 @@ use core::ffi::CStr; -use alloc::collections::btree_map::BTreeMap; +use alloc::{collections::btree_map::BTreeMap, slice, string::ToString, vec}; use lazy_static::lazy_static; +use pc_keyboard::{DecodedKey, KeyCode, KeyState}; use spin::Mutex; -use x86_64::structures::paging::{PhysFrame, Size4KiB}; - -use core::{ - future::Future, - pin::Pin, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +use x86_64::{ + align_up, + registers::model_specific::Msr, + structures::paging::{PhysFrame, Size4KiB}, }; use crate::{ - constants::syscalls::*, + constants::{memory::PAGE_SIZE, syscalls::*}, + devices::ps2_dev::keyboard, events::{ current_running_event, current_running_event_info, futures::await_on::AwaitProcess, - get_runner_time, yield_now, EventInfo, + get_runner_time, nanosleep_current_event, schedule_kernel, schedule_process, yield_now, + EventInfo, + }, + filesys::{ + get_file, + syscalls::{sys_creat, sys_open}, + FileSystem, OpenFlags, FILESYSTEM, }, - interrupts::x2apic, - memory::frame_allocator::with_buddy_frame_allocator, + interrupts::x2apic::{send_eoi, X2APIC_IA32_FS_BASE, X2APIC_IA32_GSBASE}, processes::{ - process::{sleep_process_int, sleep_process_syscall, ProcessState, PROCESS_TABLE}, + process::{ + create_process, sleep_process_int, sleep_process_syscall, ProcessState, PROCESS_TABLE, + }, registers::ForkingRegisters, }, - serial_println, - syscalls::{fork::sys_fork, memorymap::sys_mmap}, + serial_print, serial_println, + syscalls::{ + block::block_on, + fork::sys_fork, + memorymap::{sys_mmap, MmapFlags, ProtFlags}, + }, }; use core::arch::naked_asm; @@ -51,6 +62,22 @@ pub struct SyscallRegisters { pub arg6: u64, // originally in r9 } +pub struct ConstUserPtr(pub *const T); +unsafe impl Send for ConstUserPtr {} +impl From for ConstUserPtr { + fn from(value: u64) -> Self { + ConstUserPtr(value as *const T) + } +} + +pub struct MutUserPtr(pub *mut T); +unsafe impl Send for MutUserPtr {} +impl From for MutUserPtr { + fn from(value: u64) -> Self { + MutUserPtr(value as *mut T) + } +} + /// Naked syscall handler that switches to a valid kernel stack (saving /// the user stack in some TSS), saves register values, sets up /// correct arguments, and dispatches to a syscall handler @@ -159,31 +186,268 @@ pub unsafe extern "C" fn syscall_handler_impl( let syscall = unsafe { &*syscall }; let reg_vals = unsafe { &*reg_vals }; + crate::debug!("SYS {}", syscall.number); + match syscall.number as u32 { SYSCALL_EXIT => { sys_exit(syscall.arg1 as i64, reg_vals); unreachable!("sys_exit does not return"); } SYSCALL_PRINT => sys_print(syscall.arg1 as *const u8), - SYSCALL_NANOSLEEP => sys_nanosleep_64(syscall.arg1, reg_vals), + // SYSCALL_NANOSLEEP => sys_nanosleep_64(syscall.arg1, reg_vals), + SYSCALL_NANOSLEEP => block_on(sys_nanosleep(syscall.arg1), reg_vals), + // Filesystem syscalls + SYSCALL_OPEN => block_on( + sys_open( + ConstUserPtr::from(syscall.arg1), + syscall.arg2 as u32, + syscall.arg3 as u16, + ), + reg_vals, + ), + SYSCALL_CREAT => block_on( + sys_creat(ConstUserPtr::from(syscall.arg1), syscall.arg3 as u16), + reg_vals, + ), SYSCALL_FORK => sys_fork(reg_vals), - SYSCALL_MMAP => sys_mmap( - syscall.arg1, - syscall.arg2, - syscall.arg3, - syscall.arg4, - syscall.arg5 as i64, - syscall.arg6, + SYSCALL_MMAP => block_on( + sys_mmap( + syscall.arg1, + syscall.arg2, + syscall.arg3, + syscall.arg4, + syscall.arg5 as i64, + syscall.arg6, + ), + reg_vals, ), - SYSCALL_WAIT => block_on(sys_wait(syscall.arg1 as u32)), + SYSCALL_WAIT => block_on(sys_wait(syscall.arg1 as u32), reg_vals), + SYSCALL_SCHED_YIELD => block_on(sys_sched_yield(), reg_vals), SYSCALL_MUNMAP => sys_munmap(syscall.arg1, syscall.arg2), SYSCALL_MPROTECT => sys_mprotect(syscall.arg1, syscall.arg2, syscall.arg3), + SYSCALL_GETEUID => sys_geteuid(), + SYSCALL_GETUID => sys_getuid(), + SYSCALL_GETEGID => sys_getegid(), + SYSCALL_GETGID => sys_getgid(), + SYSCALL_ARCH_PRCTL => sys_arch_prctl(syscall.arg1 as i32, syscall.arg2), + SYSCALL_READ => block_on( + sys_read( + syscall.arg1 as u32, + syscall.arg2 as *mut u8, + syscall.arg3 as usize, + ), + reg_vals, + ), + SYSCALL_WRITE => block_on( + sys_write( + syscall.arg1 as u32, + syscall.arg2 as *mut u8, + syscall.arg3 as usize, + ), + reg_vals, + ), + SYSCALL_EXECVE => block_on( + sys_exec( + syscall.arg1 as *mut u8, + syscall.arg2 as *mut *mut u8, + syscall.arg3 as *mut *mut u8, + ), reg_vals), _ => { panic!("Unknown syscall, {}", syscall.number); } } } +/// # Safety +/// TODO +pub async unsafe fn sys_exec(path: *mut u8, argv: *mut *mut u8, envp: *mut *mut u8) -> u64 { + if path.is_null() { + return u64::MAX; + } + + // Find string length by looking for null terminator + let mut len = 0; + unsafe { + while *path.add(len) != 0 { + len += 1; + } + } + + // Convert to string + let bytes = unsafe { alloc::slice::from_raw_parts(path, len) }; + let pathname = match alloc::str::from_utf8(bytes) { + Ok(s) => s, + Err(_) => return u64::MAX, + }; + + // build args + let mut args = vec![]; + unsafe { + let mut i = 0; + loop { + let ptr = *argv.add(i); + if ptr.is_null() { + break; + } + let mut l = 0; + while *ptr.add(l) != 0 { + l += 1; + } + let slice = slice::from_raw_parts(&*ptr, l); + if let Ok(s) = str::from_utf8(slice) { + args.push(s.to_string()); + } + i += 1; + } + } + + serial_println!("NOT EXITING"); + + // build env vars + let mut envs = vec![]; + unsafe { + let mut i = 0; + loop { + let ptr = *envp.add(i); + if ptr.is_null() { + break; + } + let mut l = 0; + while *ptr.add(l) != 0 { + l += 1; + } + let slice = slice::from_raw_parts(&*ptr, l); + if let Ok(s) = str::from_utf8(slice) { + envs.push(s.to_string()); + } + i += 1; + } + } + serial_println!("PATHNAME: {:#?}", pathname); + serial_println!("CMD ARGS: {:#?}", args); + serial_println!("ENV VARS: {:#?}", envs); + schedule_kernel( + async { + let fs = FILESYSTEM.get().unwrap(); + let fd = { + fs.lock() + .await + .open_file( + "/executables/hello", + OpenFlags::O_RDONLY | OpenFlags::O_WRONLY, + ) + .await + }; + // if fd.is_err() { + // serial_println!("Unknown command"); + // return; + // } + serial_println!("RUNNING EXECUTABLE PLEASE HOLD"); + // At this point we assume a valid executable + // TODO: check if it is actually executable with chmod mode + let fd = fd.unwrap(); + let file = get_file(fd).unwrap(); + let file_len = { + fs.lock() + .await + .filesystem + .lock() + .get_node(&file.lock().pathname) + .await + .unwrap() + .size() + }; + sys_mmap( + 0x9000, + align_up(file_len, PAGE_SIZE as u64), + ProtFlags::PROT_EXEC.bits(), + MmapFlags::MAP_FILE.bits(), + fd as i64, + 0, + ).await; + + serial_println!("Reading file..."); + + let mut buffer = vec![0u8; file_len as usize]; + let bytes_read = { + fs.lock() + .await + .read_file(fd, &mut buffer) + .await + .expect("Failed to read file") + }; + + let buf = &buffer[..bytes_read]; + + serial_println!("Bytes read: {:#?}", bytes_read); + + let pid = create_process(buf, args, envs); + schedule_process(pid); + let _waiter = AwaitProcess::new( + pid, + get_runner_time(3_000_000_000), + current_running_event().unwrap(), + ) + .await; + }, + 3, + ); + 0 +} + +/// # Safety +/// TODO +pub async unsafe fn sys_read(fd: u32, buf: *mut u8, count: usize) -> u64 { + if fd == 0 { + let mut i = 0; + while i < count { + unsafe { + match keyboard::try_read_event().await { + Some(event) => { + if let Some(c) = event_to_ascii(&event) { + *buf.add(i) = c; + i += 1; + } + } + None => break, // Exit early + } + } + } + i as u64 + } else { + u64::MAX + } +} + +/// # Safety +/// TODO +pub async unsafe fn sys_write(fd: u32, buf: *const u8, count: usize) -> u64 { + if fd == 1 { + // STDOUT + unsafe { + let slice = core::slice::from_raw_parts(buf, count); + serial_print!("{}", core::str::from_utf8_unchecked(slice)); + } + count as u64 + } else { + u64::MAX // Error + } +} + +// Helper to convert keyboard events to ASCII +pub fn event_to_ascii(event: &keyboard::KeyboardEvent) -> Option { + if event.state != KeyState::Down { + return None; + } + + match event.decoded { + Some(DecodedKey::Unicode(c)) if c.is_ascii() => Some(c as u8), + Some(DecodedKey::RawKey(KeyCode::Return)) => Some(b'\n'), + Some(DecodedKey::RawKey(KeyCode::Backspace)) => Some(0x08), + _ => None, + } +} + pub fn sys_exit(code: i64, reg_vals: &ForkingRegisters) -> Option { // TODO handle hierarchy (parent processes), resources, threads, etc. @@ -194,7 +458,6 @@ pub fn sys_exit(code: i64, reg_vals: &ForkingRegisters) -> Option { let event: EventInfo = current_running_event_info(); - serial_println!("Process {} exited with code {}", event.pid, code); // This is for testing; this way, we can write binaries that conditionally fail tests if code == -1 { panic!("Unknown exit code, something went wrong") @@ -207,6 +470,7 @@ pub fn sys_exit(code: i64, reg_vals: &ForkingRegisters) -> Option { // Get PCB from PID let preemption_info = unsafe { let mut process_table = PROCESS_TABLE.write(); + let process = process_table .get_mut(&event.pid) .expect("Process not found"); @@ -216,9 +480,6 @@ pub fn sys_exit(code: i64, reg_vals: &ForkingRegisters) -> Option { (*pcb).state = ProcessState::Terminated; // clear_process_frames(&mut *pcb); - with_buddy_frame_allocator(|alloc| { - alloc.print_free_frames(); - }); EXIT_CODES.lock().insert(event.pid, code); REGISTER_VALUES.lock().insert(event.pid, reg_vals.clone()); @@ -233,18 +494,13 @@ pub fn sys_exit(code: i64, reg_vals: &ForkingRegisters) -> Option { core::arch::asm!( "mov rsp, {0}", "push {1}", - "stc", // Use carry flag as sentinel to run_process that we're exiting - // "ret", + "swapgs", + "ret", in(reg) preemption_info.0, in(reg) preemption_info.1 ); } - unsafe { - core::arch::asm!("swapgs"); - core::arch::asm!("ret"); - } - Some(code as u64) } @@ -261,7 +517,7 @@ pub fn sys_print(buffer: *const u8) -> u64 { /// Uses interrupt stack to restore state pub fn sys_nanosleep_32(nanos: u64, rsp: u64) -> u64 { sleep_process_int(nanos, rsp); - x2apic::send_eoi(); + send_eoi(); 0 } @@ -274,6 +530,25 @@ pub fn sys_nanosleep_64(nanos: u64, reg_vals: &ForkingRegisters) -> u64 { 0 } +pub async fn sys_nanosleep(nanos: u64) -> u64 { + unsafe { + let event = current_running_event_info(); + let mut process_table = PROCESS_TABLE.write(); + + let process = process_table + .get_mut(&event.pid) + .expect("Process not found"); + + let pcb = process.pcb.get(); + + (*pcb).state = ProcessState::Blocked; + }; + + nanosleep_current_event(nanos).unwrap().await; + + 0 +} + /// Wait on a process to finish pub async fn sys_wait(pid: u32) -> u64 { let _waiter = AwaitProcess::new( @@ -286,28 +561,75 @@ pub async fn sys_wait(pid: u32) -> u64 { return *(EXIT_CODES.lock().get(&pid).unwrap()) as u64; } -/// Helper function for sys_wait, not sure if necessary -/// TODO Ask Kiran if necessary -fn anoop_raw_waker() -> RawWaker { - fn clone(_: *const ()) -> RawWaker { - anoop_raw_waker() - } - fn wake(_: *const ()) {} - fn wake_by_ref(_: *const ()) {} - fn drop(_: *const ()) {} - let vtable = &RawWakerVTable::new(clone, wake, wake_by_ref, drop); - RawWaker::new(core::ptr::null(), vtable) +pub async fn sys_sched_yield() -> u64 { + yield_now().await; + + 0 +} + +pub fn sys_geteuid() -> u64 { + 0 +} + +pub fn sys_getuid() -> u64 { + 0 +} + +pub fn sys_getegid() -> u64 { + 0 +} + +pub fn sys_getgid() -> u64 { + 0 } -/// Helper function for sys_wait, not sure if necessary -pub fn block_on(mut future: F) -> F::Output { - let waker = unsafe { Waker::from_raw(anoop_raw_waker()) }; - let mut cx = Context::from_waker(&waker); - // Safety: we’re not moving the future while polling. - let mut future = unsafe { Pin::new_unchecked(&mut future) }; - loop { - if let Poll::Ready(val) = future.as_mut().poll(&mut cx) { - return val; + +const ARCH_SET_GS: i32 = 0x1001; +const ARCH_SET_FS: i32 = 0x1002; +const ARCH_GET_GS: i32 = 0x1003; +const ARCH_GET_FS: i32 = 0x1004; +const ARCH_CET_STATUS: i32 = 0x3001; +const ARCH_CET_DISABLE: i32 = 0x3002; +const ARCH_CET_LOCK: i32 = 0x3003; +const ARCH_CET_EXEC: i32 = 0x3004; +const ARCH_CET_ALLOC_SHSTK: i32 = 0x3005; +const ARCH_CET_PUSH_SHSTK: i32 = 0x3006; + +/// Emulate arch_prctl(2) +pub fn sys_arch_prctl(code: i32, addr: u64) -> u64 { + match code { + ARCH_SET_FS => { + // point %fs at user‐space TLS block + unsafe { Msr::new(X2APIC_IA32_FS_BASE).write(addr) }; + 0 + } + ARCH_SET_GS => { + // point %gs at user‐space TLS block (if used) + unsafe { Msr::new(X2APIC_IA32_FS_BASE).write(addr) }; + 0 + } + ARCH_GET_FS => { + // read current fs_base + let fs = unsafe { Msr::new(X2APIC_IA32_FS_BASE).read() }; + // write it back into the user buffer + let ptr = addr as *mut u64; + unsafe { ptr.write_volatile(fs) }; + 0 + } + ARCH_GET_GS => { + let gs = unsafe { Msr::new(X2APIC_IA32_GSBASE).read() }; + let ptr = addr as *mut u64; + unsafe { ptr.write_volatile(gs) }; + 0 + } + ARCH_CET_STATUS => 0, + ARCH_CET_DISABLE => 0, + ARCH_CET_LOCK => 0, + ARCH_CET_EXEC => 0, + ARCH_CET_ALLOC_SHSTK => 0, + ARCH_CET_PUSH_SHSTK => 0, + _ => { + // unknown code + 0 } - yield_now(); } } diff --git a/resources/executables/hello b/resources/executables/hello new file mode 100755 index 00000000..27b9f7b3 Binary files /dev/null and b/resources/executables/hello differ diff --git a/resources/executables/hello.c b/resources/executables/hello.c new file mode 100644 index 00000000..dafadc78 --- /dev/null +++ b/resources/executables/hello.c @@ -0,0 +1,6 @@ +#include + +int main() { + printf("Hello World!\n"); + return 0; +} diff --git a/resources/executables/ret b/resources/executables/ret new file mode 100755 index 00000000..dee274c0 Binary files /dev/null and b/resources/executables/ret differ diff --git a/resources/executables/ret.c b/resources/executables/ret.c new file mode 100644 index 00000000..4cce7f66 --- /dev/null +++ b/resources/executables/ret.c @@ -0,0 +1,3 @@ +int main() { + return 0; +} diff --git a/setup_sysroot.sh b/setup_sysroot.sh new file mode 100755 index 00000000..994c918b --- /dev/null +++ b/setup_sysroot.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -euo pipefail + +# Ensure system architecture is x86_64 +ARCH="$(uname -m)" +if [[ "${ARCH}" != "x86_64" ]]; then + echo "This script only supports x86_64 architecture" + exit 1 +fi + +SYSROOT="$HOME/linux_sysroot" +MUSL_VERSION="1.2.5" +MUSL_TAR="musl-${MUSL_VERSION}.tar.gz" +MUSL_URL="https://musl.libc.org/releases/${MUSL_TAR}" + +# Create sysroot and build dirs +echo "Creating sysroot directory at ${SYSROOT}..." +mkdir -p "${SYSROOT}" +BUILD_DIR="$(mktemp -d)" +echo "Using build directory: ${BUILD_DIR}" + +# Download musl tar +echo "Downloading and extracting musl ${MUSL_VERSION}..." +curl -LO "${MUSL_URL}" +tar -xf "${MUSL_TAR}" -C "${BUILD_DIR}" + +pushd "${BUILD_DIR}/musl-${MUSL_VERSION}" >/dev/null + +# configure for static linking +echo "Configuring musl..." +./configure --prefix=/usr --sysroot=~/linux_sysroot --disable-shared +make && make install DESTDIR=~/linux_sysroot + +popd >/dev/null + +# Update musl-gcc wrapper to point to the correct specs file +echo "Updating musl-gcc wrapper..." +MUSL_GCC_PATH="${SYSROOT}/usr/bin/musl-gcc" +if [ -f "${MUSL_GCC_PATH}" ]; then + "${SYSROOT}/usr/bin/musl-gcc" -dumpspecs >"${SYSROOT}/usr/lib/musl-gcc.specs" + sed -i 's|exec "${REALGCC:-gcc}" "$@" -specs "/usr/lib/musl-gcc.specs"|"${REALGCC:-gcc}" "$@" -specs "'${SYSROOT}'/usr/lib/musl-gcc.specs"|g' "${MUSL_GCC_PATH}" +else + echo "musl-gcc wrapper not found at ${MUSL_GCC_PATH}" +fi + +# Clean up build directory and tarball +echo "Cleaning up..." +rm -rf "${BUILD_DIR}" +rm -f "${MUSL_TAR}" + +echo "Sysroot setup complete at ${SYSROOT}." +echo "You can now statically compile C programs using musl by invoking:" +echo " ${SYSROOT}/usr/bin/musl-gcc -static -o your_program your_program.c"