diff --git a/assets/helloworld.elf b/assets/helloworld.elf new file mode 100755 index 0000000..61a550d Binary files /dev/null and b/assets/helloworld.elf differ diff --git a/kernel/src/arch/arch.rs b/kernel/src/arch/arch.rs index 88dca40..a1f1930 100644 --- a/kernel/src/arch/arch.rs +++ b/kernel/src/arch/arch.rs @@ -1,9 +1,14 @@ use crate::driver::timer::TIMER; +#[cfg(target_arch = "x86_64")] +use core::alloc::GlobalAlloc; use core::arch::asm; use limine::response::{HhdmResponse, MemoryMapResponse}; #[cfg(target_arch = "x86_64")] -use crate::arch::x86_64::{init::init_x86_64, paging::XunilFrameAllocator}; +use crate::arch::x86_64::{ + elf::run_elf_x86_64, heap::ALLOCATOR, init::init_x86_64, paging::XunilFrameAllocator, + usermode::enter_usermode_x86_64, +}; #[cfg(target_arch = "x86_64")] use x86_64::structures::paging::OffsetPageTable; @@ -15,6 +20,24 @@ pub fn init<'a>( return init_x86_64(hhdm_response, memory_map_response); } +#[cfg(target_arch = "x86_64")] +pub fn enter_usermode(user_rip: u64, user_rsp: u64) { + return enter_usermode_x86_64(user_rip, user_rsp); +} + +#[cfg(target_arch = "x86_64")] +pub fn run_elf( + entry_point: *const u8, + frame_allocator: &mut XunilFrameAllocator, + mapper: &mut OffsetPageTable, +) { + run_elf_x86_64(entry_point, frame_allocator, mapper); +} + +pub fn get_allocator<'a>() -> &'static impl GlobalAlloc { + return &ALLOCATOR; +} + pub fn idle() { unsafe { #[cfg(target_arch = "x86_64")] diff --git a/kernel/src/arch/x86_64/elf.rs b/kernel/src/arch/x86_64/elf.rs new file mode 100644 index 0000000..b1a073f --- /dev/null +++ b/kernel/src/arch/x86_64/elf.rs @@ -0,0 +1,47 @@ +use alloc::vec::Vec; +use x86_64::{ + VirtAddr, + structures::paging::{ + FrameAllocator, Mapper, OffsetPageTable, Page, PageTableFlags, PhysFrame, Size4KiB, + }, +}; + +use crate::arch::x86_64::{paging::XunilFrameAllocator, usermode::enter_usermode_x86_64}; + +pub fn run_elf_x86_64( + entry_point: *const u8, + frame_allocator: &mut XunilFrameAllocator, + mapper: &mut OffsetPageTable, +) { + let stack_base: u64 = 0x0000_7fff_0000_0000; + let page_count = 3; + let page_size = 0x1000u64; + + let mut frames: Vec> = Vec::new(); + for i in 0..page_count { + let frame = frame_allocator.allocate_frame().unwrap(); + frames.push(frame); + + let virt_addr = VirtAddr::new(stack_base + i as u64 * page_size); + let page = Page::::containing_address(virt_addr); + + unsafe { + mapper + .map_to( + page, + frame, + PageTableFlags::PRESENT + | PageTableFlags::WRITABLE + | PageTableFlags::USER_ACCESSIBLE, + frame_allocator, + ) + .unwrap() + .flush(); + } + } + + let stack_top = stack_base + (page_count as u64 * page_size); + let rsp = (stack_top & !0xF) - 8; + + enter_usermode_x86_64(entry_point as u64, rsp); +} diff --git a/kernel/src/arch/x86_64/gdt.rs b/kernel/src/arch/x86_64/gdt.rs index 6692d73..0c9c03e 100644 --- a/kernel/src/arch/x86_64/gdt.rs +++ b/kernel/src/arch/x86_64/gdt.rs @@ -9,12 +9,6 @@ use x86_64::structures::{ pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; -struct Selectors { - code_selector: SegmentSelector, - data_selector: SegmentSelector, - tss_selector: SegmentSelector, -} - lazy_static! { static ref TSS: TaskStateSegment = { let mut tss = TaskStateSegment::new(); @@ -26,23 +20,44 @@ lazy_static! { let stack_end = stack_start + STACK_SIZE as u64; stack_end }; + tss.privilege_stack_table[0] = { + const STACK_SIZE: usize = 4096 * 5; + static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; + + let stack_start = VirtAddr::from_ptr(&raw const STACK); + let stack_end = stack_start + STACK_SIZE as u64; + stack_end + }; tss }; } lazy_static! { - static ref GDT: (GlobalDescriptorTable, Selectors) = { + pub static ref GDT: ( + GlobalDescriptorTable, + ( + SegmentSelector, + SegmentSelector, + SegmentSelector, + SegmentSelector, + SegmentSelector + ) + ) = { let mut gdt = GlobalDescriptorTable::new(); - let code_selector = gdt.append(Descriptor::kernel_code_segment()); - let data_selector = gdt.append(Descriptor::kernel_data_segment()); + let kernel_code_selector = gdt.append(Descriptor::kernel_code_segment()); + let kernel_data_selector = gdt.append(Descriptor::kernel_data_segment()); + let user_code_selector = gdt.append(Descriptor::user_code_segment()); + let user_data_selector = gdt.append(Descriptor::user_data_segment()); let tss_selector = gdt.append(Descriptor::tss_segment(&TSS)); ( gdt, - Selectors { - code_selector, - data_selector, + ( + kernel_code_selector, + kernel_data_selector, + user_code_selector, + user_data_selector, tss_selector, - }, + ), ) }; } @@ -51,10 +66,18 @@ pub fn load_gdt_x86_64() { GDT.0.load(); unsafe { - CS::set_reg(GDT.1.code_selector); - DS::set_reg(GDT.1.data_selector); - ES::set_reg(GDT.1.data_selector); - SS::set_reg(GDT.1.data_selector); - load_tss(GDT.1.tss_selector); + CS::set_reg(GDT.1.0); + DS::set_reg(GDT.1.1); + ES::set_reg(GDT.1.1); + SS::set_reg(GDT.1.1); + load_tss(GDT.1.4); } } + +pub fn user_code_selector() -> SegmentSelector { + GDT.1.2 +} + +pub fn user_data_selector() -> SegmentSelector { + GDT.1.3 +} diff --git a/kernel/src/arch/x86_64/heap.rs b/kernel/src/arch/x86_64/heap.rs index 2c24d82..ade2197 100644 --- a/kernel/src/arch/x86_64/heap.rs +++ b/kernel/src/arch/x86_64/heap.rs @@ -17,7 +17,7 @@ fn align_up(addr: usize, align: usize) -> usize { } #[global_allocator] -static ALLOCATOR: Locked = Locked::new(LinkedListAllocator::new()); +pub static ALLOCATOR: Locked = Locked::new(LinkedListAllocator::new()); pub const HEAP_START: usize = 0x_4444_4444_0000; pub const HEAP_SIZE: usize = 256 * 1024 * 1024; // 256 MiB diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index 3ae642a..f684c1e 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -12,6 +12,7 @@ use pc_keyboard::DecodedKey; use pic8259::ChainedPics; use spin::Mutex; use x86_64::{ + VirtAddr, instructions::port::Port, registers::control::Cr2, structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}, @@ -48,12 +49,17 @@ lazy_static! { idt.double_fault .set_handler_fn(double_fault_handler) .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); + #[allow(function_casts_as_integer)] + idt[0x80] + .set_handler_addr(VirtAddr::new(syscall_interrupt_handler as u64)) + .set_privilege_level(x86_64::PrivilegeLevel::Ring3); } idt[InterruptIndex::Timer.as_u8()].set_handler_fn(timer_interrupt_handler); idt.page_fault.set_handler_fn(page_fault_handler); idt.general_protection_fault.set_handler_fn(gpf_handler); idt[InterruptIndex::Keyboard.as_u8()].set_handler_fn(keyboard_interrupt_handler); idt[InterruptIndex::Mouse.as_u8()].set_handler_fn(mouse_interrupt_handler); + idt.invalid_opcode.set_handler_fn(invalid_opcode_handler); idt }; } @@ -92,6 +98,10 @@ pub extern "x86-interrupt" fn gpf_handler(stack_frame: InterruptStackFrame, erro ); } +pub extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFrame) { + panic!("EXCEPTION: INVALID OPCODE\n{:#?}", stack_frame); +} + extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { TIMER.interrupt(); @@ -143,3 +153,49 @@ pub extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: Interrupt .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); } } + +#[unsafe(naked)] +unsafe extern "C" fn syscall_interrupt_handler() { + core::arch::naked_asm!( + // push all registers + "push r15", + "push r14", + "push r13", + "push r12", + "push r11", + "push r10", + "push r9", + "push r8", + "push rbp", + "push rbx", + "push rcx", + "push rdx", + "push rsi", + "push rdi", + "push rax", + "sub rsp, 8", + "mov rcx, rdx", // arg2 + "mov rdx, rsi", // arg1 + "mov rsi, rdi", // arg0 + "mov rdi, rax", // num + "call syscall_dispatch", + "add rsp, 8", + "add rsp, 8", + // pop them in reverse orser + "pop rbx", + "pop rcx", + "pop rdx", + "pop rsi", + "pop rdi", + "pop rbp", + "pop r8", + "pop r9", + "pop r10", + "pop r11", + "pop r12", + "pop r13", + "pop r14", + "pop r15", + "iretq", + ) +} diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index 1bc740e..1018a8a 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -1,6 +1,8 @@ +pub mod elf; pub mod gdt; pub mod heap; pub mod init; pub mod interrupts; pub mod mouse; pub mod paging; +pub mod usermode; diff --git a/kernel/src/arch/x86_64/usermode.rs b/kernel/src/arch/x86_64/usermode.rs new file mode 100644 index 0000000..c28afc3 --- /dev/null +++ b/kernel/src/arch/x86_64/usermode.rs @@ -0,0 +1,42 @@ +use x86_64::{ + registers::segmentation::{DS, ES, Segment}, + structures::gdt::SegmentSelector, +}; + +use crate::arch::x86_64::gdt::{user_code_selector, user_data_selector}; + +fn with_rpl3(ss: SegmentSelector) -> u64 { + (ss.0 as u64) | 3 +} + +// entry point and stack +pub fn enter_usermode_x86_64(user_rip: u64, user_rsp: u64) -> ! { + let user_cs = with_rpl3(user_code_selector()); + let user_ss = with_rpl3(user_data_selector()); + + unsafe { + DS::set_reg(user_data_selector()); + ES::set_reg(user_data_selector()); + } + + let rflags: u64 = 0x202; + + unsafe { + core::arch::asm!( + "cli", + "push {user_ss}", + "push {user_rsp}", + "push {rflags}", + "push {user_cs}", + "push {user_rip}", + "iretq", + + user_ss = in(reg) user_ss, + user_rsp = in(reg) user_rsp, + rflags = in(reg) rflags, + user_cs = in(reg) user_cs, + user_rip = in(reg) user_rip, + options(noreturn) + ); + } +} diff --git a/kernel/src/driver/elf/header.rs b/kernel/src/driver/elf/header.rs new file mode 100644 index 0000000..1355965 --- /dev/null +++ b/kernel/src/driver/elf/header.rs @@ -0,0 +1,175 @@ +// this is not made by me, but AI, im not going to type these out... + +// e_ident indices +pub const EI_MAG0: usize = 0; +pub const EI_MAG1: usize = 1; +pub const EI_MAG2: usize = 2; +pub const EI_MAG3: usize = 3; +pub const EI_CLASS: usize = 4; +pub const EI_DATA: usize = 5; +pub const EI_VERSION: usize = 6; +pub const EI_OSABI: usize = 7; + +// Magic bytes +pub const ELFMAG0: u8 = 0x7f; +pub const ELFMAG1: u8 = b'E'; +pub const ELFMAG2: u8 = b'L'; +pub const ELFMAG3: u8 = b'F'; +pub const ELF_MAGIC: [u8; 4] = [ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3]; + +// EI_CLASS +pub const ELFCLASS32: u8 = 1; +pub const ELFCLASS64: u8 = 2; + +// EI_DATA +pub const ELFDATA2LSB: u8 = 1; // little-endian +pub const ELFDATA2MSB: u8 = 2; // big-endian + +// e_type +pub const ET_NONE: u16 = 0; +pub const ET_REL: u16 = 1; +pub const ET_EXEC: u16 = 2; +pub const ET_DYN: u16 = 3; + +// e_machine +pub const EM_X86_64: u16 = 0x3E; +pub const EM_AARCH64: u16 = 0xB7; + +// p_type +pub const PT_NULL: u32 = 0; +pub const PT_LOAD: u32 = 1; +pub const PT_DYNAMIC: u32 = 2; +pub const PT_INTERP: u32 = 3; +pub const PT_NOTE: u32 = 4; +pub const PT_PHDR: u32 = 6; +pub const PT_TLS: u32 = 7; + +// p_flags +pub const PF_X: u32 = 1; +pub const PF_W: u32 = 2; +pub const PF_R: u32 = 4; + +// sh_type +pub const SHT_NULL: u32 = 0; +pub const SHT_PROGBITS: u32 = 1; +pub const SHT_SYMTAB: u32 = 2; +pub const SHT_STRTAB: u32 = 3; +pub const SHT_RELA: u32 = 4; +pub const SHT_NOBITS: u32 = 8; +pub const SHT_REL: u32 = 9; + +// sh_flags +pub const SHF_WRITE: u64 = 1; +pub const SHF_ALLOC: u64 = 2; +pub const SHF_EXECINSTR: u64 = 4; + +// st_info binding +pub const STB_LOCAL: u8 = 0; +pub const STB_GLOBAL: u8 = 1; +pub const STB_WEAK: u8 = 2; + +// st_info type +pub const STT_NOTYPE: u8 = 0; +pub const STT_OBJECT: u8 = 1; +pub const STT_FUNC: u8 = 2; +pub const STT_SECTION: u8 = 3; + +// st_shndx special values +pub const SHN_UNDEF: u16 = 0; +pub const SHN_ABS: u16 = 0xfff1; + +// x86_64 relocation types +pub const R_X86_64_NONE: u32 = 0; +pub const R_X86_64_64: u32 = 1; +pub const R_X86_64_PC32: u32 = 2; +pub const R_X86_64_PLT32: u32 = 4; +pub const R_X86_64_GLOB_DAT: u32 = 6; +pub const R_X86_64_JUMP_SLOT: u32 = 7; +pub const R_X86_64_RELATIVE: u32 = 8; +pub const R_X86_64_32: u32 = 10; +pub const R_X86_64_32S: u32 = 11; + +// Auxiliary vector types +pub const AT_PHDR: u64 = 3; +pub const AT_PHENT: u64 = 4; +pub const AT_PHNUM: u64 = 5; +pub const AT_PAGESZ: u64 = 6; +pub const AT_BASE: u64 = 7; +pub const AT_FLAGS: u64 = 8; +pub const AT_ENTRY: u64 = 9; +pub const AT_UID: u64 = 11; +pub const AT_GID: u64 = 13; +pub const AT_NULL: u64 = 0; + +#[repr(C)] +pub struct Elf64Ehdr { + pub e_ident: [u8; 16], + pub e_type: u16, + pub e_machine: u16, + pub e_version: u32, + pub e_entry: u64, + pub e_phoff: u64, + pub e_shoff: u64, + pub e_flags: u32, + pub e_ehsize: u16, + pub e_phentsize: u16, + pub e_phnum: u16, + pub e_shentsize: u16, + pub e_shnum: u16, + pub e_shstrndx: u16, +} + +#[repr(C)] +pub struct Elf64Phdr { + pub p_type: u32, + pub p_flags: u32, + pub p_offset: u64, + pub p_vaddr: u64, + pub p_paddr: u64, + pub p_filesz: u64, + pub p_memsz: u64, + pub p_align: u64, +} + +#[repr(C)] +pub struct Elf64Shdr { + pub sh_name: u32, + pub sh_type: u32, + pub sh_flags: u64, + pub sh_addr: u64, + pub sh_offset: u64, + pub sh_size: u64, + pub sh_link: u32, + pub sh_info: u32, + pub sh_addralign: u64, + pub sh_entsize: u64, +} + +#[repr(C)] +pub struct Elf64Sym { + pub st_name: u32, + pub st_info: u8, + pub st_other: u8, + pub st_shndx: u16, + pub st_value: u64, + pub st_size: u64, +} + +#[repr(C)] +pub struct Elf64Rela { + pub r_offset: u64, + pub r_info: u64, + pub r_addend: i64, +} + +#[repr(C)] +pub struct Elf64Rel { + pub r_offset: u64, + pub r_info: u64, +} + +#[repr(C)] +pub struct Elf64AuxvEntry { + pub a_type: u64, + pub a_val: u64, +} diff --git a/kernel/src/driver/elf/loader.rs b/kernel/src/driver/elf/loader.rs new file mode 100644 index 0000000..4149613 --- /dev/null +++ b/kernel/src/driver/elf/loader.rs @@ -0,0 +1,111 @@ +use core::ptr::null; + +use x86_64::structures::paging::OffsetPageTable; + +use crate::{ + arch::x86_64::paging::XunilFrameAllocator, + driver::{ + elf::{ + header::{ + ET_DYN, ET_EXEC, ET_REL, Elf64Ehdr, Elf64Rel, Elf64Shdr, SHF_ALLOC, SHT_NOBITS, + SHT_REL, + }, + program::load_program, + reloc::elf_do_reloc, + section::elf_sheader, + validation::validate_elf, + }, + syscall::{malloc, memset}, + }, +}; + +pub fn load_file( + frame_allocator: &mut XunilFrameAllocator, + mapper: &mut OffsetPageTable, + elf_bytes: &[u8], +) -> *const u8 { + // elf header size + if elf_bytes.len() < 64 { + return null(); + } + + let elf_header: &Elf64Ehdr = unsafe { &*(elf_bytes.as_ptr() as *const Elf64Ehdr) }; + + if !validate_elf(elf_header, elf_bytes.len()) { + return null(); + } + + return match elf_header.e_type { + ET_EXEC => unsafe { load_program(frame_allocator, mapper, elf_header, elf_bytes) }, + ET_DYN => return null(), // TODO + ET_REL => return null(), + _ => return null(), + }; +} + +// TODO: make ET_REL work +pub unsafe fn elf_load_stage1(hdr: *const Elf64Ehdr) { + let shdr = unsafe { elf_sheader(hdr) } as *mut Elf64Shdr; + let mut i: u16 = 0; + let shnum = unsafe { (*hdr).e_shnum }; + + while i < shnum { + let section: &mut Elf64Shdr = unsafe { &mut *(shdr.add(i as usize)) }; + + if section.sh_type == SHT_NOBITS { + if section.sh_size == 0 { + continue; + } + + if (section.sh_flags & SHF_ALLOC) != 1 { + let mem = + unsafe { malloc(section.sh_size as usize, section.sh_addralign as usize) }; + + if mem.is_null() { + continue; // handle OOM + } + + unsafe { + // zero the memory + memset(mem, 0, section.sh_size as usize); + } + section.sh_offset = (mem.addr() + hdr.addr()) as u64; + } + } + + i += 1; + } +} + +pub unsafe fn elf_load_stage2(hdr: *const Elf64Ehdr) -> i8 { + let shdr = unsafe { elf_sheader(hdr) } as *mut Elf64Shdr; + let mut i: u16 = 0; + let mut idx: u64; + let shnum = unsafe { (*hdr).e_shnum }; + + while i < shnum { + let section: &mut Elf64Shdr = unsafe { &mut *(shdr.add(i as usize)) }; + + if section.sh_type == SHT_REL { + idx = 0; + while idx < section.sh_size / section.sh_entsize { + let reltab: *const Elf64Rel = unsafe { + ((hdr as *const u8).add(section.sh_offset as usize) as *const Elf64Rel) + .add(idx as usize) + }; + + let result: i8 = unsafe { elf_do_reloc(hdr, reltab, section) }; + + if result == -1 { + return -1; + } + + idx += 1; + } + } + + i += 1; + } + + return 0; +} diff --git a/kernel/src/driver/elf/mod.rs b/kernel/src/driver/elf/mod.rs new file mode 100644 index 0000000..55fbb13 --- /dev/null +++ b/kernel/src/driver/elf/mod.rs @@ -0,0 +1,6 @@ +mod header; +pub mod loader; +mod program; +mod reloc; +mod section; +pub mod validation; diff --git a/kernel/src/driver/elf/program.rs b/kernel/src/driver/elf/program.rs new file mode 100644 index 0000000..c27a7e5 --- /dev/null +++ b/kernel/src/driver/elf/program.rs @@ -0,0 +1,110 @@ +use x86_64::{ + VirtAddr, + structures::paging::{ + FrameAllocator, Mapper, OffsetPageTable, Page, PageTableFlags, Size4KiB, mapper::MapToError, + }, +}; + +use crate::{ + arch::x86_64::paging::XunilFrameAllocator, + driver::{ + elf::header::{Elf64Ehdr, Elf64Phdr, PF_X, PT_LOAD}, + syscall::memset, + }, + util::{align_down, align_up}, +}; + +const PAGE_SIZE: u64 = 4096; + +pub unsafe fn elf_pheader(hdr: *const Elf64Ehdr) -> *const Elf64Phdr { + unsafe { (hdr as *const u8).add((*hdr).e_phoff as usize) as *const Elf64Phdr } +} + +pub unsafe fn load_program( + frame_allocator: &mut XunilFrameAllocator, + mapper: &mut OffsetPageTable, + hdr: *const Elf64Ehdr, + elf_bytes: &[u8], +) -> *const u8 { + let phdr = unsafe { elf_pheader(hdr) }; + let phnum = unsafe { (*hdr).e_phnum }; + + for i in 0..phnum { + let program_header = unsafe { phdr.add(i as usize) }; + + if unsafe { (*program_header).p_type } == PT_LOAD { + load_segment_to_memory(frame_allocator, mapper, program_header, elf_bytes); + } + } + + return unsafe { (*hdr).e_entry as *const u8 }; +} + +pub fn load_segment_to_memory( + frame_allocator: &mut XunilFrameAllocator, + mapper: &mut OffsetPageTable, + phdr: *const Elf64Phdr, + elf_bytes: &[u8], +) { + let mem_size: u64 = unsafe { (*phdr).p_memsz }; + let p_offset: u64 = unsafe { (*phdr).p_offset }; + let file_size: u64 = unsafe { (*phdr).p_filesz }; + + let vaddr: *mut u8 = unsafe { (*phdr).p_vaddr as *mut u8 }; + + if p_offset > elf_bytes.len() as u64 + || file_size > elf_bytes.len() as u64 + || p_offset + file_size > elf_bytes.len() as u64 + { + return; + } // invalid, could read past it's memory + + if file_size > mem_size { + return; + } + + let seg_start = align_down(vaddr as u64, PAGE_SIZE); + let seg_end = align_up(vaddr as u64 + mem_size, PAGE_SIZE); + + let mut flags = + PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE | PageTableFlags::WRITABLE; + + if unsafe { ((*phdr).p_flags & PF_X) != 0 } { + } else { + flags |= PageTableFlags::NO_EXECUTE; + } + + let start_page: Page = Page::containing_address(VirtAddr::new(seg_start)); + let end_page: Page = Page::containing_address(VirtAddr::new(seg_end - 1)); + let page_range = Page::range_inclusive(start_page, end_page); + + for page in page_range { + let frame = frame_allocator + .allocate_frame() + .ok_or(MapToError::::FrameAllocationFailed) + .expect("test"); + unsafe { + mapper + .map_to(page, frame, flags, frame_allocator) + .map_err(|e| e) + .expect("test") + .flush(); + } + } + + unsafe { + core::ptr::copy_nonoverlapping( + elf_bytes.as_ptr().add(p_offset as usize), + vaddr, + file_size as usize, + ); + + if mem_size > file_size { + memset( + vaddr.add(file_size as usize), + 0, + (mem_size - file_size) as usize, + ); + } + }; +} diff --git a/kernel/src/driver/elf/reloc.rs b/kernel/src/driver/elf/reloc.rs new file mode 100644 index 0000000..c860e29 --- /dev/null +++ b/kernel/src/driver/elf/reloc.rs @@ -0,0 +1,40 @@ +use crate::driver::elf::{ + header::{Elf64Ehdr, Elf64Rel, Elf64Rela, Elf64Shdr, R_X86_64_64}, + section::{elf_get_symval, elf_section}, +}; + +pub trait ArchRelocate { + fn apply_relocation(&self, rela: &Elf64Rela, base: usize, sym_value: usize) -> Result<(), i8>; + + fn setup_entry(&self, entry: usize, stack_top: usize, argv: &[&str]) -> !; +} + +// TODO: make ET_REL work + +pub unsafe fn elf_do_reloc( + hdr: *const Elf64Ehdr, + rel: *const Elf64Rel, + reltab: *mut Elf64Shdr, +) -> i8 { + let target = unsafe { elf_section(hdr, (*reltab).sh_info as usize) }; + let addr = unsafe { (hdr as *const u8).add((*target).sh_offset as usize) }; + let reference = unsafe { addr.add((*rel).r_offset as usize) as *mut u64 }; + + let symval; + + if unsafe { ((*rel).r_info) >> 32 } != 0 { + symval = unsafe { elf_get_symval(hdr, (*reltab).sh_link as u64, (*rel).r_info >> 32) }; + if symval == 1 { + return -1; + } + + match unsafe { ((*rel).r_info & 0xffff_ffff) as u32 } { + x if x == R_X86_64_64 as u32 => unsafe { *reference = symval.wrapping_add(*reference) }, + _ => return -1, + } + + return symval as i8; + } + + return 0; +} diff --git a/kernel/src/driver/elf/section.rs b/kernel/src/driver/elf/section.rs new file mode 100644 index 0000000..1900782 --- /dev/null +++ b/kernel/src/driver/elf/section.rs @@ -0,0 +1,87 @@ +use crate::driver::elf::header::{Elf64Ehdr, Elf64Shdr, Elf64Sym, SHN_ABS, SHN_UNDEF, STB_WEAK}; +use core::ffi::CStr; + +// TODO: make ET_REL work + +pub unsafe fn elf_sheader(hdr: *const Elf64Ehdr) -> *const Elf64Shdr { + unsafe { (hdr as *const u8).add((*hdr).e_shoff as usize) as *const Elf64Shdr } +} + +pub unsafe fn elf_section(hdr: *const Elf64Ehdr, idx: usize) -> *const Elf64Shdr { + unsafe { elf_sheader(hdr).add(idx) } +} + +// pub unsafe fn elf_str_table(hdr: *const Elf64Ehdr) -> *const u8 { +// if unsafe { (*hdr).e_shstrndx == SHN_UNDEF } { +// return core::ptr::null(); +// } + +// let shdr = unsafe { elf_section(hdr, (*hdr).e_shstrndx as usize) }; + +// unsafe { (hdr as *const u8).add((*shdr).sh_offset as usize) } +// } + +// pub unsafe fn elf_lookup_string(hdr: *const Elf64Ehdr, offset: usize) -> *const u8 { +// let str_tab: *const u8 = unsafe { elf_str_table(hdr) }; + +// if str_tab.is_null() { +// return core::ptr::null(); +// } + +// return unsafe { str_tab.add(offset) }; +// } + +pub fn elf_lookup_symbol(name: &CStr) -> *const u8 { + return core::ptr::null(); +} + +pub unsafe fn elf_get_symval(hdr: *const Elf64Ehdr, table: u64, idx: u64) -> u64 { + if table == SHN_UNDEF as u64 || idx == SHN_UNDEF as u64 { + return 0; + } + + let symtab: *const Elf64Shdr = unsafe { elf_section(hdr, table as usize) }; + + if unsafe { (*symtab).sh_entsize == 0 } { + return 255; // TODO: replace with error + } + + let symtab_entries: u64 = unsafe { (*symtab).sh_size / (*symtab).sh_entsize }; + + if idx >= symtab_entries { + return 255; // TODO: replace with error + } + + let symaddr = unsafe { (hdr as *const u8).add((*symtab).sh_offset as usize) }; + let sym_table = symaddr as *const Elf64Sym; + let symbol: &Elf64Sym = unsafe { &*sym_table.add(idx as usize) }; + + if symbol.st_shndx == SHN_UNDEF { + // the symbol is external + let strtab: *const Elf64Shdr = unsafe { elf_section(hdr, (*symtab).sh_link as usize) }; + let name_pointer: *const u8 = unsafe { + (hdr as *const u8) + .add((*strtab).sh_offset as usize) + .add((*symbol).st_name as usize) + }; + let name = unsafe { CStr::from_ptr(name_pointer as *const core::ffi::c_char) }; + + let target = elf_lookup_symbol(name); + if target == core::ptr::null() { + if (symbol.st_info >> 4) == STB_WEAK { + return 0; // Weak symbol initialized as 0 + } else { + return 255; // // TODO: replace with error Undefined External Symbol + } + } else { + return target as u64; + } + } else if symbol.st_shndx == SHN_ABS { + return symbol.st_value; // absolute symbol easy + } else { + let target: *const Elf64Shdr = unsafe { elf_section(hdr, symbol.st_shndx as usize) }; + return unsafe { + ((hdr as *const u8).add((symbol.st_value + (*target).sh_offset) as usize)) as u64 + }; + } +} diff --git a/kernel/src/driver/elf/validation.rs b/kernel/src/driver/elf/validation.rs new file mode 100644 index 0000000..c3a4e37 --- /dev/null +++ b/kernel/src/driver/elf/validation.rs @@ -0,0 +1,29 @@ +use crate::driver::elf::header::{ + EI_CLASS, EI_DATA, EI_VERSION, ELF_MAGIC, EM_X86_64, ET_DYN, ET_EXEC, Elf64Ehdr, +}; + +pub fn validate_elf(elf_header: &Elf64Ehdr, elf_len: usize) -> bool { + #[allow(unused_mut)] + let mut required_machine = EM_X86_64; + #[cfg(target_arch = "aarch64")] + { + use crate::driver::elf::header::EM_AARCH64; + required_machine = EM_AARCH64; + } + + elf_header.e_ident[0..4] == ELF_MAGIC + // 64 bit + && elf_header.e_ident[EI_CLASS] == 2 + // little-endian + && elf_header.e_ident[EI_DATA] == 1 + && elf_header.e_ident[EI_VERSION] == 1 + && elf_header.e_version == 1 + // check architecture + && elf_header.e_machine == required_machine + // disallow object files + && [ET_DYN, ET_EXEC].contains(&elf_header.e_type) + // standard elf64 + && elf_header.e_phentsize == 56 + && elf_header.e_phnum != 0 // zero program headers + && (elf_header.e_phoff + (elf_header.e_phnum*elf_header.e_phentsize) as u64) <= elf_len as u64 +} diff --git a/kernel/src/driver/mod.rs b/kernel/src/driver/mod.rs index e3c804d..3cdba71 100644 --- a/kernel/src/driver/mod.rs +++ b/kernel/src/driver/mod.rs @@ -1,5 +1,7 @@ +pub mod elf; pub mod graphics; pub mod keyboard; pub mod mouse; pub mod serial; +pub mod syscall; pub mod timer; diff --git a/kernel/src/driver/syscall.rs b/kernel/src/driver/syscall.rs new file mode 100644 index 0000000..9e268a0 --- /dev/null +++ b/kernel/src/driver/syscall.rs @@ -0,0 +1,73 @@ +use core::{ + alloc::{GlobalAlloc, Layout}, + ptr::null_mut, +}; + +use crate::{arch::arch::get_allocator, driver::graphics::framebuffer::with_framebuffer, println}; + +const SYS_EXIT: usize = 1; +const SYS_WRITE: usize = 60; + +pub unsafe fn malloc(size: usize, align: usize) -> *mut u8 { + let align = if align < 1 { + 1 + } else { + align.next_power_of_two() + }; + let layout = match Layout::from_size_align(size, align) { + Ok(l) => l, + Err(_) => return null_mut(), + }; + + unsafe { GlobalAlloc::alloc(get_allocator(), layout) } +} + +pub unsafe fn free(ptr: *mut u8, size: usize, align: usize) { + if ptr.is_null() { + // very important, do not double free + return; + } + + let align = if align < 1 { + 1 + } else { + align.next_power_of_two() + }; + + if let Ok(layout) = Layout::from_size_align(size.max(1), align.max(1)) { + unsafe { GlobalAlloc::dealloc(get_allocator(), ptr, layout) } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn syscall_dispatch( + num: usize, + arg0: usize, + arg1: usize, + arg2: usize, +) -> isize { + match num { + SYS_WRITE => { + let buf_ptr = arg1 as *const u8; + let len = arg2 as usize; + let bytes: &[u8] = unsafe { core::slice::from_raw_parts(buf_ptr, len) }; + let s = core::str::from_utf8(bytes).unwrap_or(""); + println!("SYS_WRITE called: {:?} {:?}", s, len); + with_framebuffer(|fb| fb.swap()); + 0 + } + SYS_EXIT => { + println!("Program exit."); + with_framebuffer(|fb| fb.swap()); + 0 + } + _ => -38, // syscall not found + } +} + +pub unsafe fn memset(ptr: *mut u8, val: u8, count: usize) { + unsafe { core::ptr::write_bytes(ptr, val, count) }; +} + +pub type Fd = i32; +pub type Off = i64; diff --git a/kernel/src/main.rs b/kernel/src/main.rs index fd000e1..b7d1297 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -15,13 +15,9 @@ pub mod driver; pub mod userspace_stub; pub mod util; -use crate::arch::arch::{infinite_idle, init, kernel_crash, sleep}; +use crate::arch::arch::{infinite_idle, init, kernel_crash}; use crate::driver::graphics::base::rgb; use crate::driver::graphics::framebuffer::{init_framebuffer, with_framebuffer}; -use crate::driver::graphics::primitives::{ - circle_filled, circle_outline, rectangle_filled, rectangle_outline, triangle_outline, -}; -use crate::driver::mouse::MOUSE; use crate::driver::serial::{ConsoleWriter, init_serial_console, with_serial_console}; use crate::driver::timer::TIMER; use crate::userspace_stub::userspace_init; @@ -98,8 +94,8 @@ unsafe extern "C" fn kmain() -> ! { // removed by the linker. assert!(BASE_REVISION.is_supported()); - let mapper; - let frame_allocator; + let mut mapper; + let mut frame_allocator; if let Some(hhdm_response) = HHDM_REQUEST.get_response() { if let Some(memory_map_response) = MEMORY_MAP_REQUEST.get_response() { @@ -125,14 +121,13 @@ unsafe extern "C" fn kmain() -> ! { println!("Could not get date at boot. Will default to 0.") } - userspace_init() + userspace_init(&mut frame_allocator, &mut mapper) } #[panic_handler] fn rust_panic(_info: &core::panic::PanicInfo) -> ! { with_framebuffer(|mut fb| { - let (width, height) = (fb.width.clone(), fb.height.clone()); - rectangle_filled(&mut fb, 0, 0, width, height, rgb(180, 0, 0)); + fb.clear(rgb(180, 0, 0)); with_serial_console(|console| { console.clear(5, 5); diff --git a/kernel/src/userspace_stub.rs b/kernel/src/userspace_stub.rs index e960fd7..6442bcb 100644 --- a/kernel/src/userspace_stub.rs +++ b/kernel/src/userspace_stub.rs @@ -1,8 +1,10 @@ use alloc::string::ToString; +use x86_64::structures::paging::OffsetPageTable; use crate::{ - arch::arch::sleep, + arch::{arch::run_elf, x86_64::paging::XunilFrameAllocator}, driver::{ + elf::loader::load_file, graphics::{ base::rgb, framebuffer::with_framebuffer, @@ -20,6 +22,7 @@ use crate::{ }; static CURSOR_BYTES: &[u8] = include_bytes!("../../assets/cursors/default.bmp"); +static TEST_ELF_BYTES: &[u8] = include_bytes!("../../assets/helloworld.elf"); const BMP_HEADER_SIZE: usize = 138; pub const CURSOR_W: usize = 24; pub const CURSOR_H: usize = 24; @@ -72,84 +75,97 @@ fn boot_animation() { }); } -pub fn userspace_init() -> ! { +pub fn userspace_init( + frame_allocator: &mut XunilFrameAllocator, + mapper: &mut OffsetPageTable, +) -> ! { // this is just a stub - boot_animation(); - let mut mouse_status = 0; - let mut width = 0; - let mut height = 0; - let mut mouse_x = 100; - let mut mouse_y = 100; + let entry_point = load_file(frame_allocator, mapper, TEST_ELF_BYTES); + println!("{:?}", entry_point); - loop { - with_serial_console(|serial_console| serial_console.clear(0, 0)); - with_framebuffer(|fb| fb.clear(rgb(253, 129, 0))); - test_performance(|| { - mouse_status = MOUSE.get_status(); - with_framebuffer(|mut fb| { - width = fb.width; - height = fb.height; + with_framebuffer(|fb| fb.swap()); - let (x_delta, y_delta) = MOUSE.take_motion(); + run_elf(entry_point, frame_allocator, mapper); - if x_delta != 0 { - mouse_x = (mouse_x as isize + x_delta as isize).max(0) as usize; - } - if y_delta != 0 { - mouse_y = (mouse_y as isize + y_delta as isize).max(0) as usize; - } - if mouse_x > width { - mouse_x = width - CURSOR_W; - } - if mouse_y > height { - mouse_y = height - CURSOR_H; - } + loop {} - rectangle_filled(&mut fb, 700, 400, 200, 200, rgb(0, 0, 0)); - rectangle_outline(&mut fb, 400, 400, 100, 100, rgb(0, 0, 0)); - circle_filled(&mut fb, 200, 200, 100, rgb(0, 0, 0)); - circle_outline(&mut fb, 400, 200, 100, rgb(0, 0, 0)); - triangle_outline(&mut fb, 100, 400, 200, 400, 150, 600, rgb(0, 0, 0)); + // boot_animation(); - let pixels = &CURSOR_BYTES[BMP_HEADER_SIZE..]; // remove header + // let mut mouse_status = 0; + // let mut width = 0; + // let mut height = 0; + // let mut mouse_x = 100; + // let mut mouse_y = 100; - for row in 0..CURSOR_H { - let src_row = (CURSOR_H - 1 - row) * CURSOR_W * 4; - for col in 0..CURSOR_W { - let i = src_row + col * 4; // 4 because rgba + // loop { + // with_serial_console(|serial_console| serial_console.clear(0, 0)); + // with_framebuffer(|fb| fb.clear(rgb(253, 129, 0))); + // test_performance(|| { + // mouse_status = MOUSE.get_status(); + // with_framebuffer(|mut fb| { + // width = fb.width; + // height = fb.height; - let b = pixels[i]; - let g = pixels[i + 1]; - let r = pixels[i + 2]; - let a = pixels[i + 3]; + // let (x_delta, y_delta) = MOUSE.take_motion(); - if a < 255 { - continue; - } + // if x_delta != 0 { + // mouse_x = (mouse_x as isize + x_delta as isize).max(0) as usize; + // } + // if y_delta != 0 { + // mouse_y = (mouse_y as isize + y_delta as isize).max(0) as usize; + // } + // if mouse_x > width { + // mouse_x = width - CURSOR_W; + // } + // if mouse_y > height { + // mouse_y = height - CURSOR_H; + // } - let color = rgb(r, g, b); + // rectangle_filled(&mut fb, 700, 400, 200, 200, rgb(0, 0, 0)); + // rectangle_outline(&mut fb, 400, 400, 100, 100, rgb(0, 0, 0)); + // circle_filled(&mut fb, 200, 200, 100, rgb(0, 0, 0)); + // circle_outline(&mut fb, 400, 200, 100, rgb(0, 0, 0)); + // triangle_outline(&mut fb, 100, 400, 200, 400, 150, 600, rgb(0, 0, 0)); - fb.put_pixel((mouse_x + col) as usize, (mouse_y + row) as usize, color); - } - } - }); + // let pixels = &CURSOR_BYTES[BMP_HEADER_SIZE..]; // remove header - let (hours, minutes, seconds) = - unix_to_hms(TIMER.get_date_at_boot() + (TIMER.now().elapsed()) / 1000); + // for row in 0..CURSOR_H { + // let src_row = (CURSOR_H - 1 - row) * CURSOR_W * 4; + // for col in 0..CURSOR_W { + // let i = src_row + col * 4; // 4 because rgba - print!( - "{:?}:{:?}:{:?}\nMouse status: {:?}\nDesktop Size: {:?}", - hours, - minutes, - seconds, - mouse_status, - (width, height) - ); - }); - with_framebuffer(|fb| { - fb.swap(); - }); - sleep(1000 / 165); - } + // let b = pixels[i]; + // let g = pixels[i + 1]; + // let r = pixels[i + 2]; + // let a = pixels[i + 3]; + + // if a < 255 { + // continue; + // } + + // let color = rgb(r, g, b); + + // fb.put_pixel((mouse_x + col) as usize, (mouse_y + row) as usize, color); + // } + // } + // }); + + // let (hours, minutes, seconds) = + // unix_to_hms(TIMER.get_date_at_boot() + (TIMER.now().elapsed()) / 1000); + + // print!( + // "{:?}:{:?}:{:?}\nMouse status: {:?}\nDesktop Size: {:?}", + // hours, + // minutes, + // seconds, + // mouse_status, + // (width, height) + // ); + // }); + // with_framebuffer(|fb| { + // fb.swap(); + // }); + // sleep(1000 / 165); + // } } diff --git a/kernel/src/util.rs b/kernel/src/util.rs index 2eef58f..2666413 100644 --- a/kernel/src/util.rs +++ b/kernel/src/util.rs @@ -46,3 +46,24 @@ pub fn test_performance(function: F) { pub fn get_bit(value: u8, position: u8) -> u8 { (value >> position) & 1 } + +#[inline] +pub const fn align_down(addr: u64, align: u64) -> u64 { + assert!(align.is_power_of_two(), "`align` must be a power of two"); + addr & !(align - 1) +} + +#[inline] +pub const fn align_up(addr: u64, align: u64) -> u64 { + assert!(align.is_power_of_two(), "`align` must be a power of two"); + let align_mask = align - 1; + if addr & align_mask == 0 { + addr + } else { + if let Some(aligned) = (addr | align_mask).checked_add(1) { + aligned + } else { + panic!("attempt to add with overflow") + } + } +} diff --git a/libxunil/.gitignore b/libxunil/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/libxunil/.gitignore @@ -0,0 +1 @@ +/target diff --git a/libxunil/Cargo.lock b/libxunil/Cargo.lock new file mode 100644 index 0000000..6ea7801 --- /dev/null +++ b/libxunil/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "libxunil" +version = "0.1.0" diff --git a/libxunil/Cargo.toml b/libxunil/Cargo.toml new file mode 100644 index 0000000..fafc857 --- /dev/null +++ b/libxunil/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "libxunil" +version = "0.1.0" +edition = "2024" + +[lib] +name = "xunil" +crate-type = ["staticlib"] + +[profile.release] +panic = "abort" +opt-level = "s" diff --git a/libxunil/src/lib.rs b/libxunil/src/lib.rs new file mode 100644 index 0000000..4f27d7e --- /dev/null +++ b/libxunil/src/lib.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +use crate::syscall::syscall3; + +pub mod syscall; + +const SYS_EXIT: usize = 1; +const SYS_WRITE: usize = 60; + +#[unsafe(no_mangle)] +extern "C" fn write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { syscall3(SYS_WRITE, fd as usize, buf as usize, count) } +} + +#[unsafe(no_mangle)] +extern "C" fn exit(code: i32) -> ! { + unsafe { syscall3(SYS_EXIT, code as usize, 0, 0) }; + loop {} +} + +#[unsafe(no_mangle)] +extern "C" fn strlen(s: *const u8) -> usize { + let mut len = 0; + while unsafe { *s.add(len) } != 0 { + len += 1; + } + len +} + +#[unsafe(no_mangle)] +extern "C" fn puts(s: *const u8) -> isize { + write(1, s, strlen(s)); + + 0 +} + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/libxunil/src/syscall.rs b/libxunil/src/syscall.rs new file mode 100644 index 0000000..3a7aa64 --- /dev/null +++ b/libxunil/src/syscall.rs @@ -0,0 +1,32 @@ +#[inline(always)] +pub unsafe fn syscall0(num: usize) -> isize { + let ret: isize; + unsafe { + core::arch::asm!( + "int 0x80", + in("rax") num, + lateout("rax") ret, + options(nostack) + ); + } + + ret +} + +#[inline(always)] +pub unsafe fn syscall3(num: usize, arg0: usize, arg1: usize, arg2: usize) -> isize { + let ret: isize; + unsafe { + core::arch::asm!( + "int 0x80", + in("rax") num, + in("rdi") arg0, + in("rsi") arg1, + in("rdx") arg2, + lateout("rax") ret, + options(nostack) + ); + } + + ret +} diff --git a/libxunil/tests/compile.sh b/libxunil/tests/compile.sh new file mode 100644 index 0000000..6f67051 --- /dev/null +++ b/libxunil/tests/compile.sh @@ -0,0 +1,4 @@ +gcc -nostdlib -nostdinc -static -no-pie \ + -o $1 $1.c \ + -L../target/release -l:libxunil.a +mv $1 ../../assets/$1.elf diff --git a/libxunil/tests/helloworld.c b/libxunil/tests/helloworld.c new file mode 100644 index 0000000..9f4caf3 --- /dev/null +++ b/libxunil/tests/helloworld.c @@ -0,0 +1,7 @@ +void write(int fd, const char* buf, unsigned long count); +void exit(int code); + +void _start() { + write(1, "Hello from C!\n", 14); + exit(0); +}