From dd596d537847b06cd6e7dc8e91db21f98e939fea Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 5 Sep 2026 10:12:42 -0500 Subject: [PATCH] feat: elf loading --- Cargo.lock | 4 + Cargo.toml | 3 + Makefile | 26 ++- limine.conf | 1 + src/arch/x86_64/apic.rs | 22 -- src/arch/x86_64/mod.rs | 31 --- src/arch/x86_64/paging.rs | 55 ++--- src/arch/x86_64/pit.rs | 14 +- src/arch/x86_64/timer.rs | 4 +- src/boot/limine.rs | 45 +++- src/format/cpio.rs | 76 +++++++ src/format/elf.rs | 423 ++++++++++++++++++++++++++++++++++++ src/format/mod.rs | 2 + src/main.rs | 211 ++---------------- src/memory/address_space.rs | 11 +- src/memory/mod.rs | 41 ++++ src/task/loader.rs | 212 ++++++++++++++++++ src/task/mod.rs | 1 + src/task/tcb.rs | 7 +- userspace/init/Cargo.toml | 9 + userspace/init/src/main.rs | 115 ++++++++++ 21 files changed, 1000 insertions(+), 313 deletions(-) create mode 100644 src/format/cpio.rs create mode 100644 src/format/elf.rs create mode 100644 src/format/mod.rs create mode 100644 src/task/loader.rs create mode 100644 userspace/init/Cargo.toml create mode 100644 userspace/init/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 0b617e6..0187296 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,10 @@ dependencies = [ "limine", ] +[[package]] +name = "init" +version = "0.1.0" + [[package]] name = "limine" version = "0.6.5" diff --git a/Cargo.toml b/Cargo.toml index 63be4f8..9183874 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "dusk" version = "0.1.0" edition = "2024" +[workspace] +members = [".", "userspace/init"] + [dependencies] limine = "0.6.5" diff --git a/Makefile b/Makefile index f71d496..982c293 100644 --- a/Makefile +++ b/Makefile @@ -6,17 +6,17 @@ MEMORY ?= 512M # In MB ISO_SIZE ?= 512 QEMU_OPTS ?= -#MKSQUASHFS_OPTS ?= GDB ?= CPUS ?= 1 # FAT type ESP_BITS ?= 32 -EXPORT_SYMBOLS = true +#EXPORT_SYMBOLS = true ISO_PATH = ${ARTIFACTS_PATH}/iso_root -#INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs +INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME} ESP_IMAGE = ${ARTIFACTS_PATH}/esp.img +USERSPACE_CARGO_OPTS = --target ${ARCH}-unknown-none CARGO_OPTS = -Zjson-target-spec --target=src/arch/${ARCH}/${ARCH}-unknown-none.json QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH} LIMINE_BOOT_VARIATION = X64 @@ -27,6 +27,7 @@ KERNEL_FILE = target/${ARCH}-unknown-none/${MODE}/dusk.elf ifeq (${MODE},release) CARGO_OPTS += --release + USERSPACE_CARGO_OPTS += --release endif ifneq (${CPUS},1) @@ -50,7 +51,7 @@ endif all: build -build: prepare-bin-files compile-bootloader compile-binaries run-scripts build-iso +build: prepare-bin-files compile-bootloader compile-binaries compile-initramfs build-iso check: cargo check -Zjson-target-spec @@ -63,13 +64,16 @@ prepare-bin-files: # Make bin/ and bin/iso_root mkdir -p ${ARTIFACTS_PATH} mkdir -p ${ISO_PATH} - # mkdir -p ${INITRAMFS_PATH} -run-scripts: - # Place the build ID into the binary so it can be read at runtime - @HASH=$$(md5sum ${KERNEL_FILE} | cut -c1-12) && \ - sed -i "s/__BUILD_ID__/$${HASH}/" ${KERNEL_FILE} + mkdir -p ${INITRAMFS_PATH} - #python scripts/initramfs-test.py 100 ${INITRAMFS_PATH}/ +compile-user: + RUSTFLAGS="-C relocation-model=static" cargo build --package init ${USERSPACE_CARGO_OPTS} + +copy-initramfs-files: compile-user + cp -v target/${ARCH}-unknown-none/${MODE}/init ${INITRAMFS_PATH}/init.elf + +compile-initramfs: copy-initramfs-files + (cd ${INITRAMFS_PATH} && find . -mindepth 1 | cpio -o -H newc) > ${ARTIFACTS_PATH}/initramfs.img copy-iso-files: # Limine files @@ -81,7 +85,7 @@ copy-iso-files: # OS files cp -v ${KERNEL_FILE} ${ISO_PATH}/boot - #cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot + cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot build-esp: copy-iso-files # Create and populate formatted FAT image for ESP partition (130048 1K-blocks = ~127MiB) diff --git a/limine.conf b/limine.conf index 7954873..771c6cf 100644 --- a/limine.conf +++ b/limine.conf @@ -4,4 +4,5 @@ timeout: 0 protocol: limine path: boot():/boot/dusk.elf + module_path: boot():/boot/initramfs.img diff --git a/src/arch/x86_64/apic.rs b/src/arch/x86_64/apic.rs index 965013b..1c6d3ca 100644 --- a/src/arch/x86_64/apic.rs +++ b/src/arch/x86_64/apic.rs @@ -183,28 +183,6 @@ impl LocalApic { self.access.write(APIC_TIMER_INITIAL_COUNT, 0); } - // pub fn delay_ticks(&self, count: u32) { - // if count == 0 { - // return; - // } - - // APIC_TIMER_COUNT.store(0, Ordering::SeqCst); - - // self.access - // .write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED); - // self.access.write(APIC_TIMER_DIVIDE_CONFIG, 0b11); - // self.access.write(APIC_TIMER_INITIAL_COUNT, count as u64); - // self.access.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64); - - // while APIC_TIMER_COUNT.load(Ordering::SeqCst) == 0 { - // unsafe { - // core::arch::asm!("sti", "hlt", "cli", options(nomem, nostack)); - // } - // } - - // self.stop_timer(); - // } - pub fn id(&self) -> u32 { self.id } diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index d33c4c4..4fb2549 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -132,37 +132,6 @@ pub fn init_interrupt_controller( }) } -// #[derive(Debug)] -// pub enum TimerError { -// DurationOverflow, -// } - -// impl InterruptController { -// pub fn delay(&self, duration: core::time::Duration) -> Result<(), TimerError> { -// let nanoseconds = duration.as_nanos(); - -// let ticks = (nanoseconds -// .checked_mul(self.local_timer_frequency as u128) -// .ok_or(TimerError::DurationOverflow)? -// + 999_999_999) -// / 1_000_000_000; - -// if ticks == 0 { -// return Ok(()); -// } - -// let mut remaining = ticks; - -// while remaining > 0 { -// let chunk = remaining.min(u32::MAX as u128) as u32; -// self.local_apic.delay_ticks(chunk); -// remaining -= chunk as u128; -// } - -// Ok(()) -// } -// } - /// # Safety /// /// The caller must ensure: diff --git a/src/arch/x86_64/paging.rs b/src/arch/x86_64/paging.rs index 0933b64..a7bbca3 100644 --- a/src/arch/x86_64/paging.rs +++ b/src/arch/x86_64/paging.rs @@ -390,16 +390,6 @@ impl PageTable { .ok_or(UnmapError::PageNotMapped) } - fn discard_private_tables( - frames: &mut [Option; MAX_INTERMEDIATE_LEVELS], - count: usize, - allocator: &mut FrameAllocator, - ) { - for frame in frames[..count].iter_mut().rev().filter_map(Option::take) { - unsafe { allocator.dealloc(frame) }; - } - } - fn apply_user_upgrades( &mut self, upgrades: &[Option; MAX_INTERMEDIATE_LEVELS], @@ -509,34 +499,18 @@ impl PageTable { let private_table_count = levels.len() - missing_depth; let mut private_tables: [Option; MAX_INTERMEDIATE_LEVELS] = core::array::from_fn(|_| None); - let mut allocated_count = 0; - - while allocated_count < private_table_count { - let private_frame = match allocator.alloc() { - Some(frame) => frame, - None => { - Self::discard_private_tables(&mut private_tables, allocated_count, allocator); - return Err(MapError::OutOfFrames); - } - }; - - if PageTableEntry::new_table( - private_frame.frame_address(), - permissions.user_accessible, - self.config, - ) - .is_err() - { - unsafe { allocator.dealloc(private_frame) }; - Self::discard_private_tables(&mut private_tables, allocated_count, allocator); - return Err(MapError::PhysicalAddressTooLarge); - } - - private_tables[allocated_count] = Some(private_frame); - allocated_count += 1; - } let prepare_result = (|| { + for slot in &mut private_tables[..private_table_count] { + let frame = allocator.alloc().ok_or(MapError::OutOfFrames)?; + let address = frame.frame_address(); + // Track ownership before validation so every error uses the same cleanup. + *slot = Some(frame); + + PageTableEntry::new_table(address, permissions.user_accessible, self.config) + .map_err(|_| MapError::PhysicalAddressTooLarge)?; + } + for private_index in 0..private_table_count { let private_frame = private_tables[private_index] .as_ref() @@ -572,7 +546,9 @@ impl PageTable { let publication_entry = match prepare_result { Ok(entry) => entry, Err(error) => { - Self::discard_private_tables(&mut private_tables, allocated_count, allocator); + for frame in private_tables.into_iter().rev().flatten() { + unsafe { allocator.dealloc(frame) }; + } return Err(error); } }; @@ -583,10 +559,7 @@ impl PageTable { [publication_location.index] = publication_entry; // The published page table now owns these frames. - for frame in private_tables[..allocated_count] - .iter_mut() - .flat_map(Option::take) - { + for frame in private_tables.into_iter().flatten() { let _ = frame.into_raw(); } diff --git a/src/arch/x86_64/pit.rs b/src/arch/x86_64/pit.rs index 9976ce7..9d06fe1 100644 --- a/src/arch/x86_64/pit.rs +++ b/src/arch/x86_64/pit.rs @@ -13,15 +13,11 @@ const BINARY: u8 = 0; pub const PIT_FREQUENCY: u64 = 1_193_182; pub const PIT_CALIBRATION_COUNT: u16 = u16::MAX; -pub struct Pit; +pub fn start_pit_one_shot(count: u16) { + unsafe { + write_u8(PIT_COMMAND, CHANNEL_0 | LOW_HIGH | MODE_0 | BINARY); -impl Pit { - pub fn start_one_shot(count: u16) { - unsafe { - write_u8(PIT_COMMAND, CHANNEL_0 | LOW_HIGH | MODE_0 | BINARY); - - write_u8(PIT_CHANNEL_0, count as u8); - write_u8(PIT_CHANNEL_0, (count >> 8) as u8); - } + write_u8(PIT_CHANNEL_0, count as u8); + write_u8(PIT_CHANNEL_0, (count >> 8) as u8); } } diff --git a/src/arch/x86_64/timer.rs b/src/arch/x86_64/timer.rs index ae462b7..63130e0 100644 --- a/src/arch/x86_64/timer.rs +++ b/src/arch/x86_64/timer.rs @@ -7,7 +7,7 @@ use crate::{ io_apic::IoApic, x86_64::{ interrupts::enable_interrupts, - pit::{PIT_CALIBRATION_COUNT, PIT_FREQUENCY, Pit}, + pit::{PIT_CALIBRATION_COUNT, PIT_FREQUENCY, start_pit_one_shot}, }, }, platform::acpi::IsaIrqRoute, @@ -37,7 +37,7 @@ pub fn calibrate_local_apic( .map_err(|_| TimerCalibrationError::IoApicNotHandled)?; local_apic.start_calibration_counter(); - Pit::start_one_shot(PIT_CALIBRATION_COUNT); + start_pit_one_shot(PIT_CALIBRATION_COUNT); enable_interrupts(); diff --git a/src/boot/limine.rs b/src/boot/limine.rs index 0cfcf97..22e9187 100644 --- a/src/boot/limine.rs +++ b/src/boot/limine.rs @@ -1,13 +1,13 @@ use ::limine as limine_api; use limine::paging::PagingMode; -use limine::request::{PagingModeRequest, RsdpRequest}; +use limine::request::{ExecutableCmdlineRequest, ModulesRequest, PagingModeRequest, RsdpRequest}; use limine_api::request::{ExecutableAddressRequest, HhdmRequest, MemmapRequest}; use limine_api::{BaseRevision, RequestsEndMarker, RequestsStartMarker}; use crate::memory::{ - KernelMemoryLayout, KernelSegment, MemoryMap, MemoryRegion, MemoryRegionKind, PagePermissions, - PhysicalAddr, VirtualAddr, + BootString, InitramfsImage, KernelMemoryLayout, KernelSegment, MemoryMap, MemoryRegion, + MemoryRegionKind, PagePermissions, PhysicalAddr, VirtualAddr, }; /// Sets the base revision to the latest revision supported by the crate. @@ -22,6 +22,14 @@ static BASE_REVISION: BaseRevision = BaseRevision::new(); #[unsafe(link_section = ".requests")] static KERNEL_ADDRESS_REQUEST: ExecutableAddressRequest = ExecutableAddressRequest::new(); +#[used] +#[unsafe(link_section = ".requests")] +static KERNEL_CMDLINE_REQUEST: ExecutableCmdlineRequest = ExecutableCmdlineRequest::new(); + +#[used] +#[unsafe(link_section = ".requests")] +static KERNEL_MODULE_REQUEST: ModulesRequest = ModulesRequest::new(); + #[used] #[unsafe(link_section = ".requests")] static HHDM_REQUEST: HhdmRequest = HhdmRequest::new(); @@ -58,8 +66,12 @@ unsafe extern "C" { static __data_end: u64; } +const MAX_COMMAND_LINE_LENGTH: usize = 512; + pub struct BootInfo { pub kernel_layout: KernelMemoryLayout, + pub command_line: BootString, + pub initramfs: InitramfsImage, pub hhdm_offset: usize, pub memory_map: MemoryMap, pub rsdp: VirtualAddr, @@ -75,6 +87,9 @@ impl BootInfo { pub enum BootError { UnsupportedBaseRevision, FailedToGetKernelAddress, + FailedToGetKernelCmdline, + FailedToGetModules, + FailedToGetInitramfs, FailedToGetHHDMAddress, FailedToGetMemmap, TooManyMemoryRegions, @@ -90,10 +105,28 @@ pub fn load_boot_info() -> Result { let kernel_address = KERNEL_ADDRESS_REQUEST .response() .ok_or(BootError::FailedToGetKernelAddress)?; + let kernel_cmdline = KERNEL_CMDLINE_REQUEST + .response() + .ok_or(BootError::FailedToGetKernelCmdline)?; + let command_line = BootString::from_bytes(kernel_cmdline.cmdline().as_bytes()); + + let modules = KERNEL_MODULE_REQUEST + .response() + .ok_or(BootError::FailedToGetModules)?; + let initramfs = modules + .modules() + .get(0) + .map(|module| { + let start = VirtualAddr::new(module.data().as_ptr() as usize); + let length = module.data().len(); + InitramfsImage { start, length } + }) + .ok_or(BootError::FailedToGetInitramfs)?; + let hhdm_offset = HHDM_REQUEST .response() .ok_or(BootError::FailedToGetHHDMAddress)? - .offset; + .offset as usize; let rsdp = RSDP_REQUEST.response().ok_or(BootError::FailedToGetRsdp)?; @@ -204,7 +237,9 @@ pub fn load_boot_info() -> Result { kernel_data_segment, ], }, - hhdm_offset: hhdm_offset as usize, + command_line, + initramfs, + hhdm_offset, memory_map, rsdp: VirtualAddr::new(rsdp.address as usize), }) diff --git a/src/format/cpio.rs b/src/format/cpio.rs new file mode 100644 index 0000000..0549a5c --- /dev/null +++ b/src/format/cpio.rs @@ -0,0 +1,76 @@ +// CPIO newc + +#[repr(C)] +struct Header { + pub c_magic: [u8; 6], + pub c_ino: [u8; 8], + pub c_mode: [u8; 8], + pub c_uid: [u8; 8], + pub c_gid: [u8; 8], + pub c_nlink: [u8; 8], + pub c_mtime: [u8; 8], + pub c_filesize: [u8; 8], + pub c_devmajor: [u8; 8], + pub c_devminor: [u8; 8], + pub c_rdevmajor: [u8; 8], + pub c_rdevminor: [u8; 8], + pub c_namesize: [u8; 8], + pub c_check: [u8; 8], +} + +impl Header { + pub fn from_bytes(bytes: &[u8]) -> Option { + if bytes.len() < core::mem::size_of::
() { + return None; + } + + let header: Header = unsafe { core::ptr::read(bytes.as_ptr() as *const Header) }; + + if header.c_magic != *b"070701" { + return None; + } + + Some(header) + } +} + +pub fn find_file<'a>(archive: &'a [u8], target: &str) -> Option<&'a [u8]> { + let mut offset = 0; + + while offset + core::mem::size_of::
() <= archive.len() { + let header = Header::from_bytes(&archive[offset..])?; + let header_start = offset; + offset += core::mem::size_of::
(); + + let file_len = + usize::from_str_radix(core::str::from_utf8(&header.c_filesize).ok()?, 16).ok()?; + let name_len = + usize::from_str_radix(core::str::from_utf8(&header.c_namesize).ok()?, 16).ok()?; + + if offset + name_len > archive.len() { + return None; + } + + let name_bytes = &archive[offset..offset + name_len]; + let name = core::str::from_utf8(name_bytes) + .ok()? + .trim_end_matches('\0'); + + if name == "TRAILER!!!" { + return None; + } + + let data_start = header_start + ((core::mem::size_of::
() + name_len + 3) & !3); + if data_start + file_len > archive.len() { + return None; + } + + if name == target { + return Some(&archive[data_start..data_start + file_len]); + } + + offset = data_start + ((file_len + 3) & !3); + } + + None +} diff --git a/src/format/elf.rs b/src/format/elf.rs new file mode 100644 index 0000000..290ac46 --- /dev/null +++ b/src/format/elf.rs @@ -0,0 +1,423 @@ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ElfIsa { + None, + Sparc, + X86, + Mips, + Ppc, + Arm, + SuperH, + Ia64, + Amd64, + AArch64, + Riscv, +} + +impl ElfIsa { + fn from_u16(value: u16) -> Result { + match value { + 0x00 => Ok(Self::None), + 0x02 => Ok(Self::Sparc), + 0x03 => Ok(Self::X86), + 0x08 => Ok(Self::Mips), + 0x14 => Ok(Self::Ppc), + 0x28 => Ok(Self::Arm), + 0x2A => Ok(Self::SuperH), + 0x32 => Ok(Self::Ia64), + 0x3E => Ok(Self::Amd64), + 0xB7 => Ok(Self::AArch64), + 0xF3 => Ok(Self::Riscv), + _ => Err(ElfError::InvalidElf), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ElfClass { + Elf32, + Elf64, +} + +impl ElfClass { + fn from_u8(value: u8) -> Result { + match value { + 1 => Ok(Self::Elf32), + 2 => Ok(Self::Elf64), + _ => Err(ElfError::InvalidElf), + } + } + + const fn header_size(self) -> u16 { + match self { + Self::Elf32 => 52, + Self::Elf64 => 64, + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Endianness { + Little, + Big, +} + +impl Endianness { + fn from_u8(value: u8) -> Result { + match value { + 1 => Ok(Self::Little), + 2 => Ok(Self::Big), + _ => Err(ElfError::InvalidElf), + } + } +} + +#[repr(u16)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ElfType { + Relocatable = 1, + Executable = 2, + SharedObject = 3, + Core = 4, +} + +impl ElfType { + fn from_u16(value: u16) -> Result { + match value { + 1 => Ok(Self::Relocatable), + 2 => Ok(Self::Executable), + 3 => Ok(Self::SharedObject), + 4 => Ok(Self::Core), + _ => Err(ElfError::InvalidElf), + } + } +} + +#[derive(Debug)] +#[allow(unused)] +pub struct ElfHeader { + magic: [u8; 4], + pub class: ElfClass, + endianness: Endianness, + version: u8, + os_abi: u8, + _reserved: [u8; 8], + pub object_type: ElfType, + pub machine: ElfIsa, + version_1: u32, + entry: u64, // 2115136 + program_header_offset: u64, // 64 + section_header_offset: u64, // 2759752 + flags: u32, // 0 + header_size: u16, // 64 + program_header_entry_size: u16, // 56 + program_header_count: u16, // 6 + section_header_entry_size: u16, // 64 + section_header_count: u16, // 17 + section_name_index: u16, // 15 +} + +#[derive(Debug)] +pub enum ElfError { + InvalidElf, +} + +impl ElfHeader { + pub fn parse(bytes: &[u8]) -> Result { + let mut reader = Reader::new(bytes); + + let magic = reader.read_array()?; + if magic != *b"\x7fELF" { + return Err(ElfError::InvalidElf); + } + + let class = ElfClass::from_u8(reader.read_u8()?)?; + let endianness = Endianness::from_u8(reader.read_u8()?)?; + reader.set_endianness(endianness); + + let version = reader.read_u8()?; + let os_abi = reader.read_u8()?; + let reserved = reader.read_array()?; + let object_type = ElfType::from_u16(reader.read_u16()?)?; + let machine = ElfIsa::from_u16(reader.read_u16()?)?; + let version_1 = reader.read_u32()?; + let entry = reader.read_word(class)?; + let program_header_offset = reader.read_word(class)?; + let section_header_offset = reader.read_word(class)?; + let flags = reader.read_u32()?; + let header_size = reader.read_u16()?; + let program_header_entry_size = reader.read_u16()?; + let program_header_count = reader.read_u16()?; + let section_header_entry_size = reader.read_u16()?; + let section_header_count = reader.read_u16()?; + let section_name_index = reader.read_u16()?; + + if header_size != class.header_size() { + return Err(ElfError::InvalidElf); + } + + Ok(Self { + magic, + class, + endianness, + version, + os_abi, + _reserved: reserved, + object_type, + machine, + version_1, + entry, + program_header_offset, + section_header_offset, + flags, + header_size, + program_header_entry_size, + program_header_count, + section_header_entry_size, + section_header_count, + section_name_index, + }) + } +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ProgramHeaderType { + Null = 0, + Load = 1, + Dynamic = 2, + Interpreter = 3, + Note = 4, + Shlib = 5, + Phdr = 6, + GnuStack = 0x6474e551, + Relro = 0x6474e552, + Other(u32), +} + +impl ProgramHeaderType { + fn from_u32(value: u32) -> Self { + match value { + 0 => Self::Null, + 1 => Self::Load, + 2 => Self::Dynamic, + 3 => Self::Interpreter, + 4 => Self::Note, + 5 => Self::Shlib, + 6 => Self::Phdr, + 0x6474e551 => Self::GnuStack, + 0x6474e552 => Self::Relro, + _ => Self::Other(value), + } + } +} + +#[derive(Debug)] +pub struct ProgramHeader { + pub segment_type: ProgramHeaderType, + pub flags: u32, + pub file_offset: u64, + pub virtual_address: u64, + _physical_address: u64, + pub file_size: u64, + pub memory_size: u64, + pub alignment: u64, +} + +impl ProgramHeader { + pub fn parse(bytes: &[u8], class: ElfClass, endianness: Endianness) -> Result { + let mut reader = Reader::new(bytes); + reader.set_endianness(endianness); + + let segment_type = ProgramHeaderType::from_u32(reader.read_u32()?); + + let flags = if class == ElfClass::Elf64 { + reader.read_u32()? + } else { + 0 + }; + + let file_offset = reader.read_word(class)?; + let virtual_address = reader.read_word(class)?; + let physical_address = reader.read_word(class)?; + let file_size = reader.read_word(class)?; + let memory_size = reader.read_word(class)?; + + let flags = if class == ElfClass::Elf32 { + reader.read_u32()? + } else { + flags + }; + + let alignment = reader.read_word(class)?; + + Ok(Self { + segment_type, + flags, + file_offset, + virtual_address, + _physical_address: physical_address, + file_size, + memory_size, + alignment, + }) + } +} + +pub struct Elf<'a> { + bytes: &'a [u8], + header: ElfHeader, +} + +impl<'a> Elf<'a> { + pub fn parse(bytes: &'a [u8]) -> Result { + let header = ElfHeader::parse(bytes)?; + + Ok(Self { bytes, header }) + } + + pub fn program_headers(&self) -> Result, ElfError> { + let offset = + usize::try_from(self.header.program_header_offset).map_err(|_| ElfError::InvalidElf)?; + let entry_size = usize::from(self.header.program_header_entry_size); + let count = usize::from(self.header.program_header_count); + + let expected_entry_size = match self.header.class { + ElfClass::Elf32 => 32, + ElfClass::Elf64 => 56, + }; + + if entry_size != expected_entry_size { + return Err(ElfError::InvalidElf); + } + + let table_size = entry_size.checked_mul(count).ok_or(ElfError::InvalidElf)?; + let table_end = offset.checked_add(table_size).ok_or(ElfError::InvalidElf)?; + let bytes = self + .bytes + .get(offset..table_end) + .ok_or(ElfError::InvalidElf)?; + + Ok(ProgramHeaders { + bytes, + class: self.header.class, + endianness: self.header.endianness, + entry_size, + remaining: count, + }) + } + + pub fn bytes(&self) -> &[u8] { + self.bytes + } + + pub fn machine(&self) -> ElfIsa { + self.header.machine + } + + pub fn entry(&self) -> usize { + self.header.entry as usize + } +} + +pub struct ProgramHeaders<'a> { + bytes: &'a [u8], + class: ElfClass, + endianness: Endianness, + entry_size: usize, + remaining: usize, +} + +impl<'a> Iterator for ProgramHeaders<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + if self.remaining == 0 { + return None; + } + + let entry = match self.bytes.get(..self.entry_size) { + Some(entry) => entry, + None => { + self.remaining = 0; + return None; + } + }; + + self.bytes = &self.bytes[self.entry_size..]; + self.remaining -= 1; + + Some(ProgramHeader::parse(entry, self.class, self.endianness)) + } + + fn size_hint(&self) -> (usize, Option) { + (self.remaining, Some(self.remaining)) + } +} + +impl ExactSizeIterator for ProgramHeaders<'_> {} + +struct Reader<'a> { + bytes: &'a [u8], + offset: usize, + endianness: Endianness, +} + +impl<'a> Reader<'a> { + const fn new(bytes: &'a [u8]) -> Self { + Self { + bytes, + offset: 0, + endianness: Endianness::Little, + } + } + + fn set_endianness(&mut self, endianness: Endianness) { + self.endianness = endianness; + } + + fn read_array(&mut self) -> Result<[u8; N], ElfError> { + let end = self.offset.checked_add(N).ok_or(ElfError::InvalidElf)?; + let bytes = self + .bytes + .get(self.offset..end) + .ok_or(ElfError::InvalidElf)?; + self.offset = end; + + bytes.try_into().map_err(|_| ElfError::InvalidElf) + } + + fn read_u8(&mut self) -> Result { + Ok(self.read_array::<1>()?[0]) + } + + fn read_u16(&mut self) -> Result { + let bytes = self.read_array()?; + Ok(match self.endianness { + Endianness::Little => u16::from_le_bytes(bytes), + Endianness::Big => u16::from_be_bytes(bytes), + }) + } + + fn read_u32(&mut self) -> Result { + let bytes = self.read_array()?; + Ok(match self.endianness { + Endianness::Little => u32::from_le_bytes(bytes), + Endianness::Big => u32::from_be_bytes(bytes), + }) + } + + fn read_u64(&mut self) -> Result { + let bytes = self.read_array()?; + Ok(match self.endianness { + Endianness::Little => u64::from_le_bytes(bytes), + Endianness::Big => u64::from_be_bytes(bytes), + }) + } + + fn read_word(&mut self, class: ElfClass) -> Result { + match class { + ElfClass::Elf32 => Ok(u64::from(self.read_u32()?)), + ElfClass::Elf64 => self.read_u64(), + } + } +} diff --git a/src/format/mod.rs b/src/format/mod.rs new file mode 100644 index 0000000..c8d7dfc --- /dev/null +++ b/src/format/mod.rs @@ -0,0 +1,2 @@ +pub mod cpio; +pub mod elf; diff --git a/src/main.rs b/src/main.rs index 668fe68..32a7415 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod arch; mod boot; mod debug; +mod format; mod memory; mod platform; mod syscall; @@ -15,10 +16,7 @@ use core::arch::global_asm; use crate::{ debug::serial, - memory::{ - AddressSpace, DirectMap, FrameAllocator, KernelStackPool, MemoryRegionKind, - PagePermissions, UserStack, VirtualAddr, - }, + memory::{AddressSpace, KernelStackPool, MemoryRegionKind, UserStack}, task::tcb::Tcb, }; @@ -92,112 +90,6 @@ pub extern "C" fn _start() -> ! { } } -unsafe extern "C" { - static task_a_start: u8; - static task_a_end: u8; - static task_b_start: u8; - static task_b_end: u8; - static task_c_start: u8; - static task_c_end: u8; -} - -unsafe fn embedded_code(start: *const u8, end: *const u8) -> &'static [u8] { - let length = unsafe { end.offset_from(start) as usize }; - unsafe { core::slice::from_raw_parts(start, length) } -} - -global_asm!( - r#" - .global task_a_start - task_a_start: - mov r12d, 100 - - .Ltask_a_loop: - mov eax, 3 - mov edi, 1 - lea rsi, [rip + .Ltask_a_message] - mov edx, 7 - xor r10d, r10d - syscall - - mov eax, 1 - syscall - - dec r12d - jnz .Ltask_a_loop - - .Ltask_a_done: - mov eax, 2 - syscall - jmp .Ltask_a_done - - .Ltask_a_message: - .ascii "task A\n" - - .global task_a_end - task_a_end: - - - .global task_b_start - task_b_start: - mov r12d, 100 - - .Ltask_b_loop: - mov eax, 3 - mov edi, 1 - lea rsi, [rip + .Ltask_b_message] - mov edx, 7 - xor r10d, r10d - syscall - - mov eax, 1 - syscall - - dec r12d - jnz .Ltask_b_loop - - .Ltask_b_done: - mov eax, 2 - syscall - jmp .Ltask_b_done - - .Ltask_b_message: - .ascii "task B\n" - - .global task_b_end - task_b_end: - - .global task_c_start - task_c_start: - mov r12d, 100 - - .Ltask_c_loop: - mov eax, 3 - mov edi, 1 - lea rsi, [rip + .Ltask_c_message] - mov edx, 7 - xor r10d, r10d - syscall - - mov eax, 1 - syscall - - dec r12d - jnz .Ltask_c_loop - - .Ltask_c_done: - mov eax, 2 - syscall - jmp .Ltask_c_done - - .Ltask_c_message: - .ascii "task C\n" - - .global task_c_end - task_c_end: - "# -); - pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { let ( mut allocator, @@ -227,6 +119,9 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { MemoryRegionKind::BootloaderReclaimable, ); + let init_code = format::cpio::find_file(boot_info.initramfs.data(), "init.elf") + .expect("Failed to load init program from initramfs"); + println!("Initializing local ACPI...",); let acpi = platform::acpi::init(&boot_info, direct_map).expect("failed to initialize ACPI"); @@ -244,95 +139,37 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space) .expect("failed to initialize interrupt controller"); - let task_a_code = unsafe { embedded_code(&task_a_start, &task_a_end) }; - let task_b_code = unsafe { embedded_code(&task_b_start, &task_b_end) }; - let task_c_code = unsafe { embedded_code(&task_c_start, &task_c_end) }; + let user_kernel_stack = kernel_stack_pool + .allocate(&mut address_space, &mut allocator) + .expect("failed to allocate task kernel stack"); - let task_a = create_test_task( - task_a_code, - &mut address_space, - &mut kernel_stack_pool, + let mut user_address_space = address_space + .new_user(&mut allocator) + .expect("failed to create user address space"); + + let image = task::loader::load_elf( + init_code, + &mut user_address_space, &mut allocator, direct_map, ); - let task_b = create_test_task( - task_b_code, - &mut address_space, - &mut kernel_stack_pool, - &mut allocator, - direct_map, + let user_stack = UserStack::allocate(&mut user_address_space, &mut allocator) + .expect("failed to allocate user stack"); + let task = Tcb::new_user( + 0, // overwritten by add_task for now + user_address_space, + user_kernel_stack, + image.expect("Failed to load elf"), + user_stack.top(), ); - let task_c = create_test_task( - task_c_code, - &mut address_space, - &mut kernel_stack_pool, - &mut allocator, - direct_map, - ); - - task::scheduler::add_task(task_a).expect("scheduler is full"); - task::scheduler::add_task(task_b).expect("scheduler is full"); - task::scheduler::add_task(task_c).expect("scheduler is full"); + task::scheduler::add_task(task).expect("scheduler is full"); task::scheduler::start(); hcf(); } -fn create_test_task( - code: &[u8], - kernel_address_space: &mut AddressSpace, - kernel_stack_pool: &mut KernelStackPool, - allocator: &mut FrameAllocator, - direct_map: DirectMap, -) -> Tcb { - assert!(code.len() <= memory::FRAME_SIZE); - - let kernel_stack = kernel_stack_pool - .allocate(kernel_address_space, allocator) - .expect("failed to allocate task kernel stack"); - - let mut user_address_space = kernel_address_space - .new_user(allocator) - .expect("failed to create user address space"); - - let user_stack = UserStack::allocate(&mut user_address_space, allocator) - .expect("failed to allocate user stack"); - - let entry = VirtualAddr::new(0x8000); - let code_frame = allocator - .alloc() - .expect("failed to allocate code frame") - .into_raw(); - - user_address_space - .map( - code_frame.start_address(), - entry, - PagePermissions::new(false, true, true), - allocator, - memory::CachePolicy::WriteBack, - ) - .expect("failed to map user code"); - - let destination = direct_map - .translate(code_frame.start_address()) - .expect("code frame outside direct map"); - - unsafe { - core::ptr::copy_nonoverlapping(code.as_ptr(), destination.as_mut_ptr(), code.len()); - } - - Tcb::new_user( - 0, // overwritten by add_task for now - user_address_space, - kernel_stack, - entry, - user_stack.top(), - ) -} - #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { println!("Uh oh, something went wrong!"); diff --git a/src/memory/address_space.rs b/src/memory/address_space.rs index 0a885b4..95c45be 100644 --- a/src/memory/address_space.rs +++ b/src/memory/address_space.rs @@ -113,13 +113,18 @@ impl AddressSpace { // undermind the permissions of the explicitly mapped kernel image if matches!( region.kind, - MemoryRegionKind::Reserved - | MemoryRegionKind::BadMemory - | MemoryRegionKind::KernelAndModules + MemoryRegionKind::Reserved | MemoryRegionKind::BadMemory ) { continue; } + // we shouldnt HHDM the kernel image, but we should map modules + if region.kind == MemoryRegionKind::KernelAndModules + && region.start == layout.segments[0].physical_base + { + continue; + } + let cache_policy = if matches!( region.kind, MemoryRegionKind::MappedReserved | MemoryRegionKind::Framebuffer diff --git a/src/memory/mod.rs b/src/memory/mod.rs index f5e4a77..3efecd1 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -3,6 +3,8 @@ mod frame; mod stack; mod user; +use core::ops::Add; + #[allow(unused)] pub use address_space::{AddressSpace, AddressSpaceCreateError, MapError, UnmapError}; pub use frame::{FRAME_SIZE, FrameAddr, FrameAllocator, OwnedFrame}; @@ -11,6 +13,37 @@ pub use stack::{KernelStack, KernelStackPool, StackCreateError, UserStack}; #[allow(unused)] pub use user::*; +pub struct BootString { + bytes: [u8; N], + len: usize, +} + +impl BootString { + pub fn from_bytes(bytes: &[u8]) -> Self { + let len = bytes.len(); + let mut boot_string = Self { bytes: [0; N], len }; + boot_string.bytes[..len].copy_from_slice(bytes); + boot_string + } + + pub fn as_str(&self) -> &str { + core::str::from_utf8(&self.bytes[..self.len]).unwrap() + } +} + +const MAX_MODULE_PATH_LENGTH: usize = 256; + +pub struct InitramfsImage { + pub start: VirtualAddr, + pub length: usize, +} + +impl InitramfsImage { + pub fn data(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.start.as_ptr(), self.length) } + } +} + pub struct KernelSegment { pub physical_base: PhysicalAddr, pub virtual_base: VirtualAddr, @@ -75,6 +108,14 @@ impl VirtualAddr { } } +impl Add for VirtualAddr { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs) + } +} + #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum MemoryRegionKind { diff --git a/src/task/loader.rs b/src/task/loader.rs new file mode 100644 index 0000000..c316d08 --- /dev/null +++ b/src/task/loader.rs @@ -0,0 +1,212 @@ +use crate::{ + format, + memory::{ + self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, OwnedFrame, PagePermissions, + VirtualAddr, + }, + println, +}; + +const MAX_LOAD_SEGMENTS: usize = 32; + +#[derive(Debug)] +struct LoadedRegion { + start: VirtualAddr, + mapped_pages: usize, +} + +#[derive(Debug)] +pub struct LoadedImage { + pub entry: VirtualAddr, + regions: [LoadedRegion; MAX_LOAD_SEGMENTS], + region_count: usize, +} + +impl LoadedImage { + /// # Safety + /// The supplied address space must contain this image's original mappings. + /// Its frames must be exclusively owned by this image and no longer in use. + pub unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) { + for region in self.regions[..self.region_count].iter().rev() { + for page in (0..region.mapped_pages).rev() { + let address = VirtualAddr::new(region.start.as_usize() + page * FRAME_SIZE); + let frame = unsafe { + address_space + .unmap(address, allocator) + .expect("loaded image mapping was unexpectedly missing") + }; + unsafe { allocator.dealloc(OwnedFrame::from_raw(frame)) }; + } + } + } +} + +#[derive(Debug)] +pub enum ElfLoadError { + AddressTranslationFailed, + FailedToMapSegment, + AddressOverflow, + InvalidStack, + OutOfMemory, + InvalidElf, + TooManyLoadSegments, +} + +#[cfg(target_arch = "x86_64")] +fn is_loadable(elf: &format::elf::Elf) -> bool { + // on x86_64, we only support ELFs that are either 32 bit x86 or 64 bit x86 + matches!( + elf.machine(), + format::elf::ElfIsa::X86 | format::elf::ElfIsa::Amd64 + ) +} + +#[cfg(not(target_arch = "x86_64"))] +fn is_loadable(elf: &format::elf::Elf) -> bool { + false +} + +pub fn load_elf( + bytes: &[u8], + user_address_space: &mut AddressSpace, + allocator: &mut FrameAllocator, + direct_map: DirectMap, +) -> Result { + let program = format::elf::Elf::parse(bytes).map_err(|_| ElfLoadError::InvalidElf)?; + if !is_loadable(&program) { + return Err(ElfLoadError::InvalidElf); + } + + let mut image = LoadedImage { + entry: VirtualAddr::new(program.entry()), + regions: core::array::from_fn(|_| LoadedRegion { + start: VirtualAddr::new(0), + mapped_pages: 0, + }), + region_count: 0, + }; + + let result = (|| { + for header in program + .program_headers() + .map_err(|_| ElfLoadError::InvalidElf)? + { + println!("Processing program header: {:?}", header); + let header = header.map_err(|_| ElfLoadError::InvalidElf)?; + + if header.file_size > header.memory_size { + return Err(ElfLoadError::InvalidElf); + } + + match header.segment_type { + format::elf::ProgramHeaderType::Load => { + if image.region_count == MAX_LOAD_SEGMENTS { + return Err(ElfLoadError::TooManyLoadSegments); + } + // TODO: give a fuck about alignment + // TODO: handle program segments that overlap + let segment_start = header.virtual_address as usize; + if (program.entry() >= segment_start + && program.entry() < segment_start + header.memory_size as usize) + && header.flags & 0x01 == 0 + { + // entry is within NX segment + return Err(ElfLoadError::InvalidElf); + } + + let page_start = segment_start & !(FRAME_SIZE - 1); + let page_offset = segment_start - page_start; + + let mapped_length = page_offset + .checked_add(header.memory_size as usize) + .ok_or(ElfLoadError::AddressOverflow)? + .div_ceil(FRAME_SIZE) + * FRAME_SIZE; + + let frame_count = mapped_length / FRAME_SIZE; + + let executable = header.flags & 0x01 != 0; + let writable = header.flags & 0x02 != 0; + // TODO: support only-executable segments + // let readable = header.flags & 0x04 != 0; + + let region = &mut image.regions[image.region_count]; + region.start = VirtualAddr::new(page_start); + image.region_count += 1; + + for i in 0..frame_count { + let frame = allocator.alloc().ok_or(ElfLoadError::OutOfMemory)?; + + println!( + "Mapping code frame: {:X?} to {:X?}", + frame, + page_start + i * FRAME_SIZE + ); + + if user_address_space + .map( + frame.frame_address().start_address(), + VirtualAddr::new(page_start + i * FRAME_SIZE), + PagePermissions::new(writable, executable, true), + allocator, + memory::CachePolicy::WriteBack, + ) + .is_err() + { + unsafe { allocator.dealloc(frame) }; + return Err(ElfLoadError::FailedToMapSegment); + } + + let _ = frame.into_raw(); + region.mapped_pages += 1; + } + + let mut copied = 0; + + while copied < header.file_size as usize { + let destination = VirtualAddr::new(segment_start + copied); + let physical = user_address_space + .to_physical(destination) + .ok_or(ElfLoadError::AddressTranslationFailed)?; + let direct_mapped = direct_map + .translate(physical) + .ok_or(ElfLoadError::AddressTranslationFailed)?; + + let page_remaining = FRAME_SIZE - destination.as_usize() % FRAME_SIZE; + let copy_length = page_remaining.min(header.file_size as usize - copied); + + unsafe { + core::ptr::copy_nonoverlapping( + program + .bytes() + .as_ptr() + .add(header.file_offset as usize + copied), + direct_mapped.as_mut_ptr(), + copy_length, + ); + } + + copied += copy_length; + } + } + format::elf::ProgramHeaderType::GnuStack => { + // if the stack is NOT R/W NX, we refuse to map it + if header.flags != 6 { + return Err(ElfLoadError::InvalidStack); + } + } + _ => {} + } + } + + Ok(()) + })(); + + if let Err(error) = result { + // Only pages created by this load are recorded; none have been handed to a task. + unsafe { image.destroy(user_address_space, allocator) }; + return Err(error); + } + + Ok(image) +} diff --git a/src/task/mod.rs b/src/task/mod.rs index d8a4d0a..b639d4a 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -1,2 +1,3 @@ +pub mod loader; pub mod scheduler; pub mod tcb; diff --git a/src/task/tcb.rs b/src/task/tcb.rs index 93da627..da4884a 100644 --- a/src/task/tcb.rs +++ b/src/task/tcb.rs @@ -1,6 +1,7 @@ use crate::{ arch::ThreadContext, memory::{AddressSpace, KernelStack, VirtualAddr}, + task::loader::LoadedImage, }; // Thread Control Block @@ -26,6 +27,7 @@ pub struct Tcb { pub kernel_stack: KernelStack, pub context: ThreadContext, pub address_space: AddressSpace, + pub image: LoadedImage, } impl Tcb { @@ -33,10 +35,10 @@ impl Tcb { id: usize, address_space: AddressSpace, kernel_stack: KernelStack, - user_entry: VirtualAddr, + image: LoadedImage, user_stack: VirtualAddr, ) -> Self { - let context = ThreadContext::new(user_entry, user_stack, kernel_stack.top()); + let context = ThreadContext::new(image.entry, user_stack, kernel_stack.top()); Self { id, @@ -44,6 +46,7 @@ impl Tcb { kernel_stack, context, address_space, + image, } } } diff --git a/userspace/init/Cargo.toml b/userspace/init/Cargo.toml new file mode 100644 index 0000000..a8a2c76 --- /dev/null +++ b/userspace/init/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "init" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "init" +test = false +bench = false diff --git a/userspace/init/src/main.rs b/userspace/init/src/main.rs new file mode 100644 index 0000000..04c79b9 --- /dev/null +++ b/userspace/init/src/main.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] + +use core::arch::asm; +use core::cell::UnsafeCell; +use core::fmt::Write; + +fn sys_yield() { + unsafe { + asm!("mov rax, 1", "syscall"); + } +} + +fn sys_write(fd: usize, buf: &str) -> Result<(), usize> { + unsafe { + let status; + + asm!( + "mov rax, 3", + "syscall", + in("rdi") fd, + in("rsi") buf.as_ptr(), + in("rdx") buf.len(), + in("r10") 0, + lateout("rax") status, + ); + + if status != 0 { Err(status) } else { Ok(()) } + } +} + +struct Writer; + +impl core::fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + sys_write(1, s).map_err(|_| core::fmt::Error)?; + Ok(()) + } +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => (let _ = $crate::Writer.write_fmt(format_args!($($arg)*));); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +pub fn sys_exit(exit_code: usize) -> ! { + unsafe { + asm!( + "mov rax, 2", + "syscall", + in("rdi") exit_code, + options(noreturn) + ); + } +} + +struct Heap { + pub data: UnsafeCell<[u8; 1024]>, +} + +impl Heap { + const fn new() -> Self { + Self { + data: UnsafeCell::new([0; 1024]), + } + } + + fn len(&self) -> usize { + unsafe { (*self.data.get()).len() } + } +} + +impl core::ops::Deref for Heap { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + unsafe { (*self.data.get()).as_ref() } + } +} + +unsafe impl Sync for Heap {} + +static HEAP: Heap = Heap::new(); + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + (0..100).for_each(|i| { + println!("Hello {}", i); + sys_yield(); + }); + + let len = HEAP.len(); + for i in 0..len { + unsafe { HEAP.data.get().as_mut().unwrap()[i] = i as u8 }; + } + + (0..1024).for_each(|i| { + println!("{}", unsafe { HEAP.data.get().as_ref().unwrap()[i] }); + sys_yield(); + }); + + sys_exit(0); +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + println!("{info}"); + sys_exit(1); +}