feat(kernel): implement ELF64 loader, user space paging, and Ring 3 jump via iretq

This commit is contained in:
RarDog
2026-09-02 16:49:04 +03:00
parent 9cffb01889
commit 6cbd13b4aa
6 changed files with 281 additions and 25 deletions
+64
View File
@@ -0,0 +1,64 @@
/* opencoreC - Ring 3 User Mode Transition (IRETQ) */
.global enter_user_mode
.extern set_kernel_stack
.section .text
/*
* void enter_user_mode(uint64_t entry_rip, uint64_t user_rsp, uint64_t pml4_paddr, uint64_t kernel_stack_top);
* RDI = entry_rip
* RSI = user_rsp
* RDX = pml4_paddr
* RCX = kernel_stack_top
*/
enter_user_mode:
/* 1. Save kernel stack in TSS (RSP0) for future interrupts/syscalls */
push rdi
push rsi
push rdx
mov rdi, rcx
call set_kernel_stack
pop rdx
pop rsi
pop rdi
/* 2. Switch to User Address Space (PML4) */
mov cr3, rdx
/* 3. Build IRETQ Stack Frame:
* [RSP + 32] = SS (User Data: 0x18 | 3 = 0x1B)
* [RSP + 24] = RSP (User Stack Pointer)
* [RSP + 16] = RFLAGS (Interrupt Flag Enabled: 0x202)
* [RSP + 8] = CS (User Code: 0x20 | 3 = 0x23)
* [RSP + 0] = RIP (User Entry Point)
*/
mov ax, 0x1B
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push 0x1B /* User SS */
push rsi /* User RSP */
push 0x0202 /* RFLAGS (IF enabled) */
push 0x23 /* User CS */
push rdi /* User RIP */
/* 4. Clear general-purpose registers */
xor rax, rax
xor rbx, rbx
xor rcx, rcx
xor rdx, rdx
xor rsi, rsi
xor rdi, rdi
xor rbp, rbp
xor r8, r8
xor r9, r9
xor r10, r10
xor r11, r11
xor r12, r12
xor r13, r13
xor r14, r14
xor r15, r15
/* 5. Jump to Ring 3 */
iretq
+2 -1
View File
@@ -94,7 +94,8 @@ pub unsafe fn init() {
); );
} }
pub fn set_kernel_stack(stack_top: u64) { #[no_mangle]
pub extern "C" fn set_kernel_stack(stack_top: u64) {
unsafe { unsafe {
let tss_ptr = core::ptr::addr_of_mut!(TSS); let tss_ptr = core::ptr::addr_of_mut!(TSS);
(*tss_ptr).rsp0 = stack_top; (*tss_ptr).rsp0 = stack_top;
+135
View File
@@ -0,0 +1,135 @@
//! ELF64 Executable Binary Parser and Loader for Ring 3 User Space
use crate::mm::vmm::{VMM, PAGE_PRESENT, PAGE_WRITABLE, PAGE_USER};
use crate::mm::pfa::{PFA, PAGE_SIZE};
use crate::kprintln;
#[repr(C, packed)]
pub struct Elf64Header {
pub ident: [u8; 16],
pub elf_type: u16,
pub machine: u16,
pub version: u32,
pub entry: u64,
pub phoff: u64,
pub shoff: u64,
pub flags: u32,
pub ehsize: u16,
pub phentsize: u16,
pub phnum: u16,
pub shentsize: u16,
pub shnum: u16,
pub shstrndx: u16,
}
#[repr(C, packed)]
pub struct Elf64ProgramHeader {
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,
}
pub const PT_LOAD: u32 = 1;
pub const PF_X: u32 = 1 << 0;
pub const PF_W: u32 = 1 << 1;
pub const PF_R: u32 = 1 << 2;
pub struct LoadedProcess {
pub entry_rip: u64,
pub user_rsp: u64,
pub pml4_paddr: u64,
pub kernel_stack_top: u64,
}
pub unsafe fn load_elf(elf_bytes: &[u8], hhdm_offset: u64) -> Result<LoadedProcess, &'static str> {
if elf_bytes.len() < core::mem::size_of::<Elf64Header>() {
return Err("ELF file too small");
}
let header = &*(elf_bytes.as_ptr() as *const Elf64Header);
// Validate ELF Magic "\x7fELF"
if header.ident[0] != 0x7F || header.ident[1] != b'E' || header.ident[2] != b'L' || header.ident[3] != b'F' {
return Err("Invalid ELF magic");
}
// Must be 64-bit (class 2) and x86_64 machine (0x3E)
if header.ident[4] != 2 || header.machine != 0x3E {
return Err("Not a 64-bit x86_64 ELF");
}
let vmm_ptr = core::ptr::addr_of_mut!(VMM);
let pml4_paddr = (*vmm_ptr).create_user_address_space().ok_or("Failed to allocate user PML4")?;
let phoff = header.phoff as usize;
let phentsize = header.phentsize as usize;
let phnum = header.phnum as usize;
let entry_rip = header.entry;
for i in 0..phnum {
let phdr_ptr = elf_bytes.as_ptr().add(phoff + i * phentsize) as *const Elf64ProgramHeader;
let phdr = &*phdr_ptr;
if phdr.p_type == PT_LOAD {
let vaddr_start = phdr.p_vaddr;
let memsz = phdr.p_memsz as usize;
let filesz = phdr.p_filesz as usize;
let file_offset = phdr.p_offset as usize;
let mut flags = PAGE_PRESENT | PAGE_USER;
if (phdr.p_flags & PF_W) != 0 {
flags |= PAGE_WRITABLE;
}
// Map and copy segment page by page
let page_count = (memsz + PAGE_SIZE - 1) / PAGE_SIZE;
for page in 0..page_count {
let page_vaddr = vaddr_start + (page * PAGE_SIZE) as u64;
let page_paddr = (*core::ptr::addr_of_mut!(PFA)).alloc_frame().ok_or("Out of memory for ELF segment")?;
// Map in user page table
(*vmm_ptr).map_page(pml4_paddr, page_vaddr, page_paddr, flags)
.map_err(|_| "Failed to map user ELF page")?;
// Copy data from file
let dest = (page_paddr + hhdm_offset) as *mut u8;
let page_file_start = page * PAGE_SIZE;
if page_file_start < filesz {
let copy_len = core::cmp::min(PAGE_SIZE, filesz - page_file_start);
let src = elf_bytes.as_ptr().add(file_offset + page_file_start);
core::ptr::copy_nonoverlapping(src, dest, copy_len);
}
}
}
}
// Allocate User Stack: 4 pages at 0x00007FFF_0000_0000
let user_stack_base = 0x00007FFF_0000_0000u64;
let user_stack_pages = 4;
for page in 0..user_stack_pages {
let page_vaddr = user_stack_base + (page * PAGE_SIZE) as u64;
let page_paddr = (*core::ptr::addr_of_mut!(PFA)).alloc_frame().ok_or("Out of memory for user stack")?;
(*vmm_ptr).map_page(pml4_paddr, page_vaddr, page_paddr, PAGE_PRESENT | PAGE_WRITABLE | PAGE_USER)
.map_err(|_| "Failed to map user stack")?;
}
let user_rsp = user_stack_base + (user_stack_pages * PAGE_SIZE) as u64 - 16;
// Allocate Kernel Stack (TSS RSP0): 4 pages
let kstack_frame = (*core::ptr::addr_of_mut!(PFA)).alloc_frame().ok_or("Out of memory for kernel stack")?;
let kernel_stack_top = kstack_frame + hhdm_offset + (PAGE_SIZE as u64) - 16;
kprintln!("[LOADER] ELF Loaded: Entry RIP: {:#x}, User RSP: {:#x}, User PML4: {:#x}",
entry_rip, user_rsp, pml4_paddr);
Ok(LoadedProcess {
entry_rip,
user_rsp,
pml4_paddr,
kernel_stack_top,
})
}
+1
View File
@@ -0,0 +1 @@
pub mod elf;
+41 -21
View File
@@ -7,6 +7,7 @@ pub mod arch;
pub mod mm; pub mod mm;
pub mod ipc; pub mod ipc;
pub mod sched; pub mod sched;
pub mod loader;
use core::panic::PanicInfo; use core::panic::PanicInfo;
use limine_requests::*; use limine_requests::*;
@@ -14,6 +15,16 @@ use limine_requests::*;
// Include assembly trampolines directly into the binary // Include assembly trampolines directly into the binary
core::arch::global_asm!(include_str!("../asm/context.S")); core::arch::global_asm!(include_str!("../asm/context.S"));
core::arch::global_asm!(include_str!("../asm/syscall_entry.S")); core::arch::global_asm!(include_str!("../asm/syscall_entry.S"));
core::arch::global_asm!(include_str!("../asm/ring3_enter.S"));
extern "C" {
fn enter_user_mode(
entry_rip: u64,
user_rsp: u64,
pml4_paddr: u64,
kernel_stack_top: u64,
) -> !;
}
#[no_mangle] #[no_mangle]
pub extern "C" fn _start() -> ! { pub extern "C" fn _start() -> ! {
@@ -64,26 +75,7 @@ pub extern "C" fn _start() -> ! {
// 7. Initialize Scheduler // 7. Initialize Scheduler
sched::init(); sched::init();
// 8. Inspect Boot Modules (Initial Userspace Servers) // 8. Self-Test Fast-Path IPC Ping
unsafe {
let mod_resp = MODULE_REQUEST.response;
if !mod_resp.is_null() {
let count = (*mod_resp).module_count;
kprintln!("[BOOT] Found {} initial userspace module(s):", count);
for i in 0..count {
let mod_file = *(*mod_resp).modules.add(i as usize);
let path_cstr = (*mod_file).path;
let path_len = (0..256).find(|&j| *path_cstr.add(j) == 0).unwrap_or(0);
let path = core::str::from_utf8(core::slice::from_raw_parts(path_cstr, path_len)).unwrap_or("unknown");
kprintln!(" - Module [{}]: {} (size: {} bytes, addr: {:p})",
i, path, (*mod_file).size, (*mod_file).address);
}
} else {
kprintln!("[BOOT] No external modules passed by bootloader.");
}
}
// 9. Kernel Self-Test: Fast-Path IPC Ping
kprintln!("[TEST] Executing Kernel Fast-Path IPC Self-Test..."); kprintln!("[TEST] Executing Kernel Fast-Path IPC Self-Test...");
let (status, resp_op, val) = ipc::fastpath::handle_fastpath_call( let (status, resp_op, val) = ipc::fastpath::handle_fastpath_call(
ipc::fastpath::CAP_KERNEL_CONTROL, ipc::fastpath::CAP_KERNEL_CONTROL,
@@ -92,7 +84,35 @@ pub extern "C" fn _start() -> ! {
); );
kprintln!("[TEST] IPC Fast-Path Ping Result: status={}, opcode={:#x}, val={}", status, resp_op, val); kprintln!("[TEST] IPC Fast-Path Ping Result: status={}, opcode={:#x}, val={}", status, resp_op, val);
kprintln!("[KERNEL] Initialization complete. Entering kernel idle loop.\n"); // 9. Inspect and Load Initial Userspace Process (Ring 3 Transition)
unsafe {
let mod_resp = MODULE_REQUEST.response;
if !mod_resp.is_null() && (*mod_resp).module_count > 0 {
kprintln!("[BOOT] Loading root userspace module [0] into Ring 3...");
let mod_file = *(*mod_resp).modules;
let elf_slice = core::slice::from_raw_parts((*mod_file).address, (*mod_file).size as usize);
match loader::elf::load_elf(elf_slice, hhdm_offset) {
Ok(proc) => {
kprintln!("[BOOT] Transitioning CPU to Ring 3 (User Mode) via IRETQ...");
kprintln!("-------------------------------------------------------");
enter_user_mode(
proc.entry_rip,
proc.user_rsp,
proc.pml4_paddr,
proc.kernel_stack_top,
);
}
Err(err) => {
kprintln!("[ERROR] Failed to load ELF process: {}", err);
}
}
} else {
kprintln!("[BOOT] No userspace modules detected. Staying in Ring 0 idle loop.");
}
}
kprintln!("[KERNEL] Entering idle loop.\n");
loop { loop {
unsafe { unsafe {
+38 -3
View File
@@ -29,10 +29,43 @@ impl VirtualMemoryManager {
} }
#[inline] #[inline]
fn phys_to_virt<T>(&self, paddr: u64) -> *mut T { pub fn phys_to_virt<T>(&self, paddr: u64) -> *mut T {
(paddr + self.hhdm_offset) as *mut T (paddr + self.hhdm_offset) as *mut T
} }
#[inline]
pub fn read_cr3(&self) -> u64 {
let cr3: u64;
unsafe {
core::arch::asm!("mov {0}, cr3", out(reg) cr3, options(nostack, preserves_flags));
}
cr3
}
#[inline]
pub fn load_cr3(&self, pml4_paddr: u64) {
unsafe {
core::arch::asm!("mov cr3, {0}", in(reg) pml4_paddr, options(nostack, preserves_flags));
}
}
/// Create a new user PML4 with higher-half kernel space cloned
pub unsafe fn create_user_address_space(&self) -> Option<u64> {
let pfa_ptr = core::ptr::addr_of_mut!(PFA);
let user_pml4_paddr = (*pfa_ptr).alloc_frame()?;
let user_pml4 = self.phys_to_virt::<PageTable>(user_pml4_paddr);
let current_cr3 = self.read_cr3() & PAGE_MASK;
let kernel_pml4 = self.phys_to_virt::<PageTable>(current_cr3);
// Copy kernel mappings (higher half: 256..512)
for i in 256..512 {
(*user_pml4).entries[i] = (*kernel_pml4).entries[i];
}
Some(user_pml4_paddr)
}
/// Map a single 4KiB page into the specified PML4 /// Map a single 4KiB page into the specified PML4
pub unsafe fn map_page( pub unsafe fn map_page(
&self, &self,
@@ -63,8 +96,10 @@ impl VirtualMemoryManager {
// 4. Map the physical page in the PT // 4. Map the physical page in the PT
(*pt).entries[pt_idx] = (paddr & PAGE_MASK) | flags | PAGE_PRESENT; (*pt).entries[pt_idx] = (paddr & PAGE_MASK) | flags | PAGE_PRESENT;
// Invalidate TLB for this virtual address // Invalidate TLB for this virtual address if current address space
core::arch::asm!("invlpg [{0}]", in(reg) vaddr, options(nostack, preserves_flags)); if (self.read_cr3() & PAGE_MASK) == pml4_paddr {
core::arch::asm!("invlpg [{0}]", in(reg) vaddr, options(nostack, preserves_flags));
}
Ok(()) Ok(())
} }