feat: userspace program loading and task spawning
This commit is contained in:
+91
-149
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user