Something went wrong. Try again.
A custom OS written in Zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241use core::{num::NonZero, ops::Range, ptr::NonNull, sync::atomic::{AtomicBool, Ordering}};use elf::{ElfBytes, endian::AnyEndian};use nodit::interval::ie;use x86_64::{PhysAddr, VirtAddr, registers::{control::{Efer, EferFlags}, rflags::RFlags}};use crate::{elf_segment_flags::ElfSegmentFlags, elf_segment_type::ElfSegmentType, enter_user_mode::{EnterUserModeInput, enter_user_mode}, hhdm_offset::hhdm_offset, limine_requests::{MODULE_REQUEST, USER_MODE_PROGRAM_0_PATH}, memory::{MEMORY, paging::{ConfigurableFlags, Frame, Page, PageSize, PatMemoryType}, physical_memory::MemoryType}, syscall_handler, translate_addr::{OffsetMappedPhysAddr, OffsetMappedVirtAddr}, x86_64_consts::LOWER_HALF_END};
static CONSUMED_USER_MODE_PROGRAM_0: AtomicBool = AtomicBool::new(false);
pub fn run_program_0() { // Parse the ELF let module = MODULE_REQUEST .response() .unwrap() .modules() .iter() .find(|module| module.path() == USER_MODE_PROGRAM_0_PATH.to_str().unwrap()) .unwrap(); let elf_bytes = module.data(); let elf = ElfBytes::<AnyEndian>::minimal_parse(elf_bytes).expect("ELF should be valid");
// Create a new address space for the user mode program let memory = MEMORY.get().unwrap(); let mut physical_memory = memory.physical_memory.lock(); let mut user_l4 = memory.virtual_memory.lock().l4_mut().new_user( physical_memory .get_user_mode_program_frame_allocator() .allocate_4kib_frame() .unwrap() );
let previously_consumed = CONSUMED_USER_MODE_PROGRAM_0.swap(false, Ordering::Relaxed); assert!(!previously_consumed);
// Remove the module from physical memory map // We will only be using 4 KiB pages because most ELFs will have segments // only aligned to 4 KiB, and Limine only aligns the ELF to 4 KiB let page_size = PageSize::_4KiB; let module_physical_interval = { let hhdm_offset: u64 = hhdm_offset().into(); let start = (VirtAddr::from_ptr(module.data().as_ptr()) - hhdm_offset).as_u64(); ie( start, (start + module.data().len() as u64).next_multiple_of(page_size.byte_len_u64()) ) }; let _ = physical_memory.map_mut().cut(&module_physical_interval);
// Map ELF segments let mut range_to_zero: Option<Range<PhysAddr>> = None;
for segment in elf.segments().unwrap() { if ElfSegmentType::try_from(segment.p_type) != Ok(ElfSegmentType::Load) { continue; }
// Make sure the segment is only referencing file memory contained within the ELF assert!(segment.p_offset + segment.p_filesz <= module.data().len() as u64);
let start_page = Page::new( VirtAddr::new(segment.p_vaddr).align_down(page_size.byte_len_u64()), page_size ) .unwrap();
let file_pages_len = if segment.p_filesz > 0 { (segment.p_vaddr + segment.p_filesz).div_ceil(page_size.byte_len_u64()) - segment.p_vaddr / page_size.byte_len_u64() } else { 0 };
let start_frame = Frame::new( (VirtAddr::from_ptr(module.data().as_ptr()).offset_mapped() + segment.p_offset).align_down(page_size.byte_len_u64()), page_size ) .unwrap();
// Map the virtual memory to the ELF's frames let flags = ElfSegmentFlags::from(segment); let mut frame_allocator = physical_memory.get_user_mode_program_frame_allocator(); let flags = ConfigurableFlags { pat_memory_type: PatMemoryType::WriteBack, writable: flags.contains(ElfSegmentFlags::WRITABLE), executable: flags.contains(ElfSegmentFlags::EXECUTABLE) };
for i in 0..file_pages_len { let page = start_page.offset(i).unwrap(); let frame = start_frame.offset(i).unwrap(); unsafe { user_l4.map_page(page, frame, flags, &mut frame_allocator) }.unwrap(); }
// Mark the ELF's frames as used by user mode if file_pages_len > 0 { let interval = { let start = start_frame.start_addr().as_u64(); ie(start, start + file_pages_len * page_size.byte_len_u64()) };
let _ = physical_memory.map_mut().cut(&interval); physical_memory .map_mut() .insert_merge_touching_if_values_equal(interval, MemoryType::UsedByUserMode) .unwrap(); }
if segment.p_memsz > segment.p_filesz { if segment.p_filesz > 0 { // We need to zero any remaining bytes from that frame assert_eq!( range_to_zero, None, "there can only be up to 1 segment with p_memsz > p_filesz" );
range_to_zero = Some({ let start = VirtAddr::from_ptr(module.data().as_ptr()).offset_mapped() + segment.p_offset + segment.p_filesz; start..start.align_up(page_size.byte_len_u64()) }); }
// We need to allocate, zero, and map additional frames let extra_pages_len = (segment.p_vaddr + segment.p_memsz) .div_ceil(page_size.byte_len_u64()) - (segment.p_vaddr + segment.p_filesz).div_ceil(page_size.byte_len_u64()); let start_page = start_page.offset(file_pages_len).unwrap();
for i in 0..extra_pages_len { let page = start_page.offset(i).unwrap(); let frame = physical_memory .allocate_frame_with_type(page_size, MemoryType::UsedByUserMode) .unwrap(); let frame_ptr = NonNull::new(frame.offset_mapped().start_addr().as_mut_ptr::<u8>()).unwrap(); // Safety: we own the frame unsafe { frame_ptr.write_bytes(0, page_size.byte_len()) }; let mut frame_allocator = physical_memory.get_user_mode_program_frame_allocator(); unsafe { user_l4.map_page(page, frame, flags, &mut frame_allocator) }.unwrap(); } } }
// Map all non-referenced frames in the ELF as usable // Currently all non-referenced frames are gaps in the map // We just need to "fill the gaps" with usable loop { let gap = physical_memory .map_mut() .gaps_trimmed(&module_physical_interval) .next(); if let Some(gap) = gap { physical_memory .map_mut() .insert_merge_touching_if_values_equal(gap, MemoryType::Usable) .unwrap(); } else { break; } }
// Parse the entry point before we modify the ELF, in case the modification corrupts the ELF metadata let entry_point = NonZero::new(elf.ehdr.e_entry).expect("entry point should be defined in ELF");
// Zero the range we need to zero, if needed if let Some(range_to_zero) = range_to_zero { let count = (range_to_zero.end - range_to_zero.start) as usize; let ptr = NonNull::new(range_to_zero.start.offset_mapped().as_mut_ptr::<u8>()).unwrap(); // Safety: we now have exclusive access to the ELF bytes unsafe { ptr.write_bytes(0, count) }; }
// Zero the unused bytes of the last frame of the ELF module, if it's used // For simplicity we will just unconditionally zero it { let start = module.data().as_ptr().addr() + module.data().len(); let ptr = NonNull::new(start as *mut u8).unwrap(); let end = start.next_multiple_of(page_size.byte_len()); let count = end - start; // Safety: we have exclusive access to this memory unsafe { ptr.write_bytes(0, count) }; }
// Allocate a stack // Technically this stack placement could overlap with our ELF, but we will // assume it won't let rsp = LOWER_HALF_END;
{ let stack_size: u64 = 64 * 0x400; // We are using 4 KiB pages because we need <2 MiB, but we could use any // page size for the stack, as long as the stack size is a multiple of it let page_size = PageSize::_4KiB; let pages_len = stack_size.div_ceil(page_size.byte_len_u64()); let start_page = Page::new( VirtAddr::new(rsp - pages_len * page_size.byte_len_u64()), page_size ) .unwrap();
for i in 0..pages_len { let page = start_page.offset(i).unwrap(); let frame = physical_memory .allocate_frame_with_type(page_size, MemoryType::UsedByUserMode) .unwrap();
let flags = ConfigurableFlags { pat_memory_type: PatMemoryType::WriteBack, writable: true, executable: false };
let mut frame_allocator = physical_memory.get_user_mode_program_frame_allocator(); unsafe { user_l4.map_page(page, frame, flags, &mut frame_allocator) }.unwrap(); } }
// Enable syscall in IA32_EFER // https://shell-storm.org/x86doc/SYSCALL.html // https://wiki.osdev.org/CPU_Registers_x86-64#IA32_EFER unsafe { Efer::update(|flags| { *flags = flags.union(EferFlags::SYSTEM_CALL_EXTENSIONS); }) };
// Drop the lock to the physical memory drop(physical_memory);
// Switch to the user address space // Safety: we can still reference kernel memory unsafe { user_l4.switch_to(memory.new_kernel_cr3_flags) };
syscall_handler::init();
let input = EnterUserModeInput { rflags: RFlags::empty(), rip: entry_point.get(), rsp: rsp };
unsafe { enter_user_mode(input) };}