feat: userspace program loading and task spawning

This commit is contained in:
Zoe
2026-09-07 08:24:46 -05:00
parent 028ac8fb13
commit 3308fd2959
28 changed files with 1605 additions and 676 deletions
+91 -149
View File
@@ -1,80 +1,61 @@
use crate::{
format,
memory::{
self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, PagePermissions, UserStack,
VirtualAddr,
self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, InitramfsImage, PagePermissions,
UserStack, VirtualAddr,
},
println,
task::tcb::Tcb,
};
pub fn spawn(
name: &str,
initramfs: &[u8],
initramfs: &InitramfsImage,
kernel_as: &mut AddressSpace,
allocator: &mut FrameAllocator,
direct_map: DirectMap,
stacks: &mut crate::memory::KernelStackPool,
) -> usize {
let bytes = format::cpio::find_file(initramfs, name)
let bytes = format::cpio::find_file(initramfs.data(), name)
.unwrap_or_else(|| panic!("{name} missing from initramfs"));
let kernel_stack = stacks
.allocate(kernel_as, allocator)
let kernel_stack = crate::task::scheduler::allocate_kernel_stack(kernel_as, allocator)
.expect("kernel stack allocation failed");
let initramfs_physical_addr = kernel_as
.to_physical(initramfs.start)
.expect("failed to translate initramfs start address");
let mut address_space = kernel_as
.new_user(allocator)
.expect("address space allocation failed");
address_space
.map_range(
initramfs_physical_addr,
VirtualAddr::new(0x4000_0000),
((initramfs.length) + 0xFFF) & !0xFFF,
PagePermissions::new(true, false, true),
allocator,
memory::CachePolicy::WriteBack,
)
.expect("failed to map initramfs");
let user_stack =
UserStack::allocate(&mut address_space, allocator).expect("user stack allocation failed");
let entry = load_elf(bytes, &mut address_space, allocator, direct_map).expect("invalid ELF");
let task = Tcb::new_user(0, address_space, kernel_stack, entry, user_stack.top());
crate::task::scheduler::add_task(task).expect("scheduler is full")
}
let as_id =
crate::memory::insert_address_space(address_space).expect("address space table is full");
pub fn root(
initramfs: &[u8],
kernel_as: &mut AddressSpace,
allocator: &mut FrameAllocator,
direct_map: DirectMap,
stacks: &mut crate::memory::KernelStackPool,
) {
spawn(
"omega3.elf",
initramfs,
kernel_as,
allocator,
direct_map,
stacks,
);
let task = Tcb::new_user(0, as_id, kernel_stack, entry, user_stack.top());
crate::task::scheduler::add_task(task).expect("scheduler is full")
}
#[derive(Debug)]
enum ElfLoadError {
AddressTranslationFailed,
FailedToMapSegment,
AddressOverflow,
InvalidStack,
OutOfMemory,
InvalidElf,
}
#[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
}
fn load_elf(
bytes: &[u8],
user_address_space: &mut AddressSpace,
@@ -82,113 +63,74 @@ fn load_elf(
direct_map: DirectMap,
) -> Result<VirtualAddr, ElfLoadError> {
let program = format::elf::Elf::parse(bytes).map_err(|_| ElfLoadError::InvalidElf)?;
if !is_loadable(&program) {
let mut executable_entry = false;
for segment in program.segments() {
let segment = segment.map_err(|_| ElfLoadError::InvalidElf)?;
let end = segment
.address
.checked_add(segment.memory_size)
.filter(|&end| end <= memory::USER_SPACE_END.as_usize())
.ok_or(ElfLoadError::InvalidElf)?;
if segment.memory_size == 0 {
continue;
}
executable_entry |= segment.executable && (segment.address..end).contains(&program.entry);
let page_start = segment.address & !(FRAME_SIZE - 1);
let file_end = segment.address + segment.data.len();
let permissions = PagePermissions::new(segment.writable, segment.executable, true);
// Overlapping segment pages are rejected by map(), including stack/archive collisions.
for page in (page_start..end).step_by(FRAME_SIZE) {
let frame = allocator.alloc_nozero().ok_or(ElfLoadError::OutOfMemory)?;
let physical = frame.frame_address().start_address();
let Some(destination) = direct_map.translate(physical) else {
unsafe { allocator.dealloc(frame) };
return Err(ElfLoadError::AddressTranslationFailed);
};
let copy_start = page.max(segment.address).min(page + FRAME_SIZE);
let copy_end = (page + FRAME_SIZE).min(file_end).max(copy_start);
let prefix = copy_start - page;
let copied = copy_end - copy_start;
unsafe {
let destination = destination.as_mut_ptr::<u8>();
// Initialize padding and BSS, but don't zero bytes we're about to overwrite.
core::ptr::write_bytes(destination, 0, prefix);
if copied != 0 {
core::ptr::copy_nonoverlapping(
segment.data.as_ptr().add(copy_start - segment.address),
destination.add(prefix),
copied,
);
}
core::ptr::write_bytes(
destination.add(prefix + copied),
0,
FRAME_SIZE - prefix - copied,
);
}
if user_address_space
.map(
physical,
VirtualAddr::new(page),
permissions,
allocator,
memory::CachePolicy::WriteBack,
)
.is_err()
{
unsafe { allocator.dealloc(frame) };
return Err(ElfLoadError::FailedToMapSegment);
}
let _ = frame.into_raw();
}
}
if !executable_entry {
return Err(ElfLoadError::InvalidElf);
}
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 => {
// 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;
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();
}
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(VirtualAddr::new(program.entry()))
Ok(VirtualAddr::new(program.entry))
}