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
+5 -2
View File
@@ -7,15 +7,18 @@ use dusk_sys::{println, sys_exit, sys_recv, sys_send};
pub extern "C" fn _start() -> ! {
let msg = "Hello from client!";
println!("[client] Sent: {}", msg);
sys_send(0, msg.as_ptr() as usize, msg.len()).unwrap();
// TODO: we assume the echo server is task 1 (spawned by omega3)
sys_send(1, msg.as_ptr() as usize, msg.len()).unwrap();
let out = [0u8; 128];
let out_ptr = out.as_ptr() as usize;
let max_len = out.len();
let (actual_len, sender) = sys_recv(out_ptr, max_len).unwrap();
let (actual_len, _) = sys_recv(out_ptr, max_len).unwrap();
println!(
"[client] Received: {}",
core::str::from_utf8(&out[..actual_len]).unwrap()
);
sys_exit(0);
}
+167 -4
View File
@@ -12,6 +12,14 @@ pub enum Status {
OutOfMemory = 5,
}
// Opaque handle type
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Handle(usize);
// our own address space and thread handle are always given to us
pub const SELF_AS: Handle = Handle(0);
pub const SELF_THREAD: Handle = Handle(1);
impl From<usize> for Status {
fn from(value: usize) -> Self {
match value {
@@ -25,6 +33,21 @@ impl From<usize> for Status {
}
}
#[repr(u64)]
pub enum SyscallNumber {
Yield = 1,
Exit = 2,
Write = 3,
Send = 4,
Recv = 5,
FrameAlloc = 6,
FrameDealloc = 7,
AsCreate = 8,
Map = 9,
Unmap = 10,
TaskCreate = 11,
}
pub fn sys_yield() {
unsafe {
asm!(
@@ -46,7 +69,7 @@ fn debug_write(buf: &str) -> Result<(), Status> {
in("rsi") buf.as_ptr(),
in("rdx") buf.len(),
in("r10") 0,
inlateout("rax") 3usize => status,
inlateout("rax") SyscallNumber::Write as usize => status,
lateout("rcx") _,
lateout("r11") _
);
@@ -96,7 +119,7 @@ pub fn sys_exit(exit_code: usize) -> ! {
asm!(
"syscall",
in("rdi") exit_code,
in("rax") 2usize,
in("rax") SyscallNumber::Exit as usize,
options(noreturn)
);
}
@@ -111,7 +134,7 @@ pub fn sys_send(dest_task_id: usize, msg_ptr: usize, len: usize) -> Result<(), S
in("rdi") dest_task_id,
in("rsi") msg_ptr,
in("rdx") len,
inlateout("rax") 4usize => status,
inlateout("rax") SyscallNumber::Send as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
@@ -137,7 +160,7 @@ pub fn sys_recv(buf_ptr: usize, max_len: usize) -> Result<(usize, usize), Status
in("rsi") max_len,
in("rdx") &raw mut actual_len as usize,
in("r10") &raw mut sender as usize,
inlateout("rax") 5usize => status,
inlateout("rax") SyscallNumber::Recv as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
@@ -149,3 +172,143 @@ pub fn sys_recv(buf_ptr: usize, max_len: usize) -> Result<(usize, usize), Status
}
}
}
pub fn sys_frame_alloc() -> Result<Handle, Status> {
let mut handle: usize = 0;
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") &raw mut handle as usize,
inlateout("rax") SyscallNumber::FrameAlloc as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(Handle(handle))
}
}
}
pub fn sys_frame_dealloc(frame_handle: Handle) -> Result<(), Status> {
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") frame_handle.0,
inlateout("rax") SyscallNumber::FrameDealloc as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(())
}
}
}
pub fn sys_as_create() -> Result<Handle, Status> {
let mut handle: usize = 0;
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") &raw mut handle as usize,
inlateout("rax") SyscallNumber::AsCreate as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(Handle(handle))
}
}
}
pub fn sys_map(
as_handle: Handle,
frame_handle: Handle,
virtual_addr: usize,
permissions: usize,
) -> Result<(), Status> {
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") as_handle.0,
in("rsi") frame_handle.0,
in("rdx") virtual_addr,
in("r10") permissions,
inlateout("rax") SyscallNumber::Map as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(())
}
}
}
pub fn sys_unmap(as_handle: Handle, virtual_addr: usize) -> Result<(), Status> {
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") as_handle.0,
in("rsi") virtual_addr,
inlateout("rax") SyscallNumber::Unmap as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(())
}
}
}
pub fn sys_task_create(
as_handle: Handle,
entry: usize,
user_stack: usize,
) -> Result<Handle, Status> {
let mut handle: usize = 0;
unsafe {
let status: usize;
asm!(
"syscall",
in("rdi") as_handle.0,
in("rsi") entry,
in("rdx") user_stack,
in("r10") &raw mut handle as usize,
inlateout("rax") SyscallNumber::TaskCreate as usize => status,
lateout("rcx") _,
lateout("r11") _,
);
if status != 0 {
Err(status.into())
} else {
Ok(Handle(handle))
}
}
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "echo"
version = "0.1.0"
edition = "2024"
[dependencies]
dusk-sys = { path = "../dusk-sys" }
[[bin]]
name = "echo"
test = false
bench = false
+23
View File
@@ -0,0 +1,23 @@
#![no_std]
#![no_main]
use dusk_sys::{println, sys_exit, sys_recv, sys_send};
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
let out = [0u8; 128];
loop {
let (actual_len, sender) = sys_recv(out.as_ptr() as usize, out.len()).unwrap();
println!(
"[echo] Received: {}",
core::str::from_utf8(&out[..actual_len]).unwrap()
);
sys_send(sender, out.as_ptr() as usize, actual_len).unwrap();
}
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{info}");
sys_exit(1);
}
+73
View File
@@ -0,0 +1,73 @@
// CPIO newc archive parser
#[repr(C)]
struct Header {
pub c_magic: [u8; 6],
pub c_ino: [u8; 8],
pub c_mode: [u8; 8],
pub c_uid: [u8; 8],
pub c_gid: [u8; 8],
pub c_nlink: [u8; 8],
pub c_mtime: [u8; 8],
pub c_filesize: [u8; 8],
pub c_devmajor: [u8; 8],
pub c_devminor: [u8; 8],
pub c_rdevmajor: [u8; 8],
pub c_rdevminor: [u8; 8],
pub c_namesize: [u8; 8],
pub c_check: [u8; 8],
}
impl Header {
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < core::mem::size_of::<Header>() {
return None;
}
let header: Header = unsafe { core::ptr::read(bytes.as_ptr() as *const Header) };
if header.c_magic != *b"070701" {
return None;
}
Some(header)
}
}
pub fn find_file<'a>(archive: *const u8, target: &str) -> Option<&'a [u8]> {
let mut offset = 0;
loop {
let header = Header::from_bytes(&unsafe {
core::slice::from_raw_parts(archive.add(offset), core::mem::size_of::<Header>())
})?;
let header_start = offset;
offset += core::mem::size_of::<Header>();
let file_len =
usize::from_str_radix(core::str::from_utf8(&header.c_filesize).ok()?, 16).ok()?;
let name_len =
usize::from_str_radix(core::str::from_utf8(&header.c_namesize).ok()?, 16).ok()?;
let name_bytes = &unsafe { core::slice::from_raw_parts(archive.add(offset), name_len) };
let name = core::str::from_utf8(name_bytes)
.ok()?
.trim_end_matches('\0');
if name == "TRAILER!!!" {
break;
}
let data_start = header_start + ((core::mem::size_of::<Header>() + name_len + 3) & !3);
if name == target {
return Some(&unsafe {
core::slice::from_raw_parts(archive.add(data_start), file_len)
});
}
offset = data_start + ((file_len + 3) & !3);
}
None
}
+425
View File
@@ -0,0 +1,425 @@
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ElfIsa {
None,
Sparc,
X86,
Mips,
Ppc,
Arm,
SuperH,
Ia64,
Amd64,
AArch64,
Riscv,
}
impl ElfIsa {
fn from_u16(value: u16) -> Result<Self, ElfError> {
match value {
0x00 => Ok(Self::None),
0x02 => Ok(Self::Sparc),
0x03 => Ok(Self::X86),
0x08 => Ok(Self::Mips),
0x14 => Ok(Self::Ppc),
0x28 => Ok(Self::Arm),
0x2A => Ok(Self::SuperH),
0x32 => Ok(Self::Ia64),
0x3E => Ok(Self::Amd64),
0xB7 => Ok(Self::AArch64),
0xF3 => Ok(Self::Riscv),
_ => Err(ElfError::InvalidElf),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ElfClass {
Elf32,
Elf64,
}
impl ElfClass {
fn from_u8(value: u8) -> Result<Self, ElfError> {
match value {
1 => Ok(Self::Elf32),
2 => Ok(Self::Elf64),
_ => Err(ElfError::InvalidElf),
}
}
const fn header_size(self) -> u16 {
match self {
Self::Elf32 => 52,
Self::Elf64 => 64,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Endianness {
Little,
Big,
}
impl Endianness {
fn from_u8(value: u8) -> Result<Self, ElfError> {
match value {
1 => Ok(Self::Little),
2 => Ok(Self::Big),
_ => Err(ElfError::InvalidElf),
}
}
}
#[repr(u16)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ElfType {
Relocatable = 1,
Executable = 2,
SharedObject = 3,
Core = 4,
}
impl ElfType {
fn from_u16(value: u16) -> Result<Self, ElfError> {
match value {
1 => Ok(Self::Relocatable),
2 => Ok(Self::Executable),
3 => Ok(Self::SharedObject),
4 => Ok(Self::Core),
_ => Err(ElfError::InvalidElf),
}
}
}
#[derive(Debug)]
#[allow(unused)]
pub struct ElfHeader {
magic: [u8; 4],
pub class: ElfClass,
endianness: Endianness,
version: u8,
os_abi: u8,
_reserved: [u8; 8],
pub object_type: ElfType,
pub machine: ElfIsa,
version_1: u32,
entry: u64, // 2115136
program_header_offset: u64, // 64
section_header_offset: u64, // 2759752
flags: u32, // 0
header_size: u16, // 64
program_header_entry_size: u16, // 56
program_header_count: u16, // 6
section_header_entry_size: u16, // 64
section_header_count: u16, // 17
section_name_index: u16, // 15
}
#[derive(Debug)]
pub enum ElfError {
InvalidElf,
}
impl ElfHeader {
pub fn parse(bytes: &[u8]) -> Result<Self, ElfError> {
let mut reader = Reader::new(bytes);
let magic = reader.read_array()?;
if magic != *b"\x7fELF" {
return Err(ElfError::InvalidElf);
}
let class = ElfClass::from_u8(reader.read_u8()?)?;
let endianness = Endianness::from_u8(reader.read_u8()?)?;
reader.set_endianness(endianness);
let version = reader.read_u8()?;
let os_abi = reader.read_u8()?;
let reserved = reader.read_array()?;
let object_type = ElfType::from_u16(reader.read_u16()?)?;
let machine = ElfIsa::from_u16(reader.read_u16()?)?;
let version_1 = reader.read_u32()?;
let entry = reader.read_word(class)?;
let program_header_offset = reader.read_word(class)?;
let section_header_offset = reader.read_word(class)?;
let flags = reader.read_u32()?;
let header_size = reader.read_u16()?;
let program_header_entry_size = reader.read_u16()?;
let program_header_count = reader.read_u16()?;
let section_header_entry_size = reader.read_u16()?;
let section_header_count = reader.read_u16()?;
let section_name_index = reader.read_u16()?;
if header_size != class.header_size() {
return Err(ElfError::InvalidElf);
}
Ok(Self {
magic,
class,
endianness,
version,
os_abi,
_reserved: reserved,
object_type,
machine,
version_1,
entry,
program_header_offset,
section_header_offset,
flags,
header_size,
program_header_entry_size,
program_header_count,
section_header_entry_size,
section_header_count,
section_name_index,
})
}
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProgramHeaderType {
Null = 0,
Load = 1,
Dynamic = 2,
Interpreter = 3,
Note = 4,
Shlib = 5,
Phdr = 6,
GnuStack = 0x6474e551,
Relro = 0x6474e552,
Other(u32),
}
impl ProgramHeaderType {
fn from_u32(value: u32) -> Self {
match value {
0 => Self::Null,
1 => Self::Load,
2 => Self::Dynamic,
3 => Self::Interpreter,
4 => Self::Note,
5 => Self::Shlib,
6 => Self::Phdr,
0x6474e551 => Self::GnuStack,
0x6474e552 => Self::Relro,
_ => Self::Other(value),
}
}
}
#[derive(Debug)]
#[allow(unused)]
pub struct ProgramHeader {
pub segment_type: ProgramHeaderType,
pub flags: u32,
pub file_offset: u64,
pub virtual_address: u64,
_physical_address: u64,
pub file_size: u64,
pub memory_size: u64,
pub alignment: u64,
}
impl ProgramHeader {
pub fn parse(bytes: &[u8], class: ElfClass, endianness: Endianness) -> Result<Self, ElfError> {
let mut reader = Reader::new(bytes);
reader.set_endianness(endianness);
let segment_type = ProgramHeaderType::from_u32(reader.read_u32()?);
let flags = if class == ElfClass::Elf64 {
reader.read_u32()?
} else {
0
};
let file_offset = reader.read_word(class)?;
let virtual_address = reader.read_word(class)?;
let physical_address = reader.read_word(class)?;
let file_size = reader.read_word(class)?;
let memory_size = reader.read_word(class)?;
let flags = if class == ElfClass::Elf32 {
reader.read_u32()?
} else {
flags
};
let alignment = reader.read_word(class)?;
Ok(Self {
segment_type,
flags,
file_offset,
virtual_address,
_physical_address: physical_address,
file_size,
memory_size,
alignment,
})
}
}
pub struct Elf<'a> {
bytes: &'a [u8],
header: ElfHeader,
}
impl<'a> Elf<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, ElfError> {
let header = ElfHeader::parse(bytes)?;
Ok(Self { bytes, header })
}
pub fn program_headers(&self) -> Result<ProgramHeaders<'_>, ElfError> {
let offset =
usize::try_from(self.header.program_header_offset).map_err(|_| ElfError::InvalidElf)?;
let entry_size = usize::from(self.header.program_header_entry_size);
let count = usize::from(self.header.program_header_count);
let expected_entry_size = match self.header.class {
ElfClass::Elf32 => 32,
ElfClass::Elf64 => 56,
};
if entry_size != expected_entry_size {
return Err(ElfError::InvalidElf);
}
let table_size = entry_size.checked_mul(count).ok_or(ElfError::InvalidElf)?;
let table_end = offset.checked_add(table_size).ok_or(ElfError::InvalidElf)?;
let bytes = self
.bytes
.get(offset..table_end)
.ok_or(ElfError::InvalidElf)?;
Ok(ProgramHeaders {
bytes,
class: self.header.class,
endianness: self.header.endianness,
entry_size,
remaining: count,
})
}
pub fn bytes(&self) -> &[u8] {
self.bytes
}
#[allow(unused)]
pub fn machine(&self) -> ElfIsa {
self.header.machine
}
pub fn entry(&self) -> usize {
self.header.entry as usize
}
}
pub struct ProgramHeaders<'a> {
bytes: &'a [u8],
class: ElfClass,
endianness: Endianness,
entry_size: usize,
remaining: usize,
}
impl<'a> Iterator for ProgramHeaders<'a> {
type Item = Result<ProgramHeader, ElfError>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
let entry = match self.bytes.get(..self.entry_size) {
Some(entry) => entry,
None => {
self.remaining = 0;
return None;
}
};
self.bytes = &self.bytes[self.entry_size..];
self.remaining -= 1;
Some(ProgramHeader::parse(entry, self.class, self.endianness))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl ExactSizeIterator for ProgramHeaders<'_> {}
struct Reader<'a> {
bytes: &'a [u8],
offset: usize,
endianness: Endianness,
}
impl<'a> Reader<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
offset: 0,
endianness: Endianness::Little,
}
}
fn set_endianness(&mut self, endianness: Endianness) {
self.endianness = endianness;
}
fn read_array<const N: usize>(&mut self) -> Result<[u8; N], ElfError> {
let end = self.offset.checked_add(N).ok_or(ElfError::InvalidElf)?;
let bytes = self
.bytes
.get(self.offset..end)
.ok_or(ElfError::InvalidElf)?;
self.offset = end;
bytes.try_into().map_err(|_| ElfError::InvalidElf)
}
fn read_u8(&mut self) -> Result<u8, ElfError> {
Ok(self.read_array::<1>()?[0])
}
fn read_u16(&mut self) -> Result<u16, ElfError> {
let bytes = self.read_array()?;
Ok(match self.endianness {
Endianness::Little => u16::from_le_bytes(bytes),
Endianness::Big => u16::from_be_bytes(bytes),
})
}
fn read_u32(&mut self) -> Result<u32, ElfError> {
let bytes = self.read_array()?;
Ok(match self.endianness {
Endianness::Little => u32::from_le_bytes(bytes),
Endianness::Big => u32::from_be_bytes(bytes),
})
}
fn read_u64(&mut self) -> Result<u64, ElfError> {
let bytes = self.read_array()?;
Ok(match self.endianness {
Endianness::Little => u64::from_le_bytes(bytes),
Endianness::Big => u64::from_be_bytes(bytes),
})
}
fn read_word(&mut self, class: ElfClass) -> Result<u64, ElfError> {
match class {
ElfClass::Elf32 => Ok(u64::from(self.read_u32()?)),
ElfClass::Elf64 => self.read_u64(),
}
}
}
+84 -12
View File
@@ -1,7 +1,19 @@
#![no_std]
#![no_main]
use dusk_sys::{println, sys_exit, sys_recv, sys_send};
mod cpio;
mod elf;
use dusk_sys::{
Handle, SELF_AS, println, sys_as_create, sys_exit, sys_frame_alloc, sys_map, sys_task_create,
sys_unmap, sys_yield,
};
// Mapped into the root task's address space by the kernel.
static INITRAMFS_START: usize = 0x4000_0000;
const SCRATCH_PAGE: usize = 0x8000_0000;
const STACK_TOP: usize = 0x0000_7FFF_FFFF_F000;
const STACK_PAGES: usize = 4;
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
@@ -14,21 +26,81 @@ pub extern "C" fn _start() -> ! {
println!(r#" d88888 88888b "Y88888P" "#);
println!(r#"----- Omega3 Dusk Root Server"#);
let buf = [0u8; 128];
let buf_ptr = buf.as_ptr() as usize;
let max_len = buf.len();
loop {
let (actual_len, sender) = sys_recv(buf_ptr, max_len).unwrap();
println!(
"[server] Received: {}",
core::str::from_utf8(&buf[..actual_len]).unwrap()
);
sys_send(sender, buf_ptr, actual_len).unwrap();
}
let echo_bytes = cpio::find_file(INITRAMFS_START as *const u8, "echo.elf").unwrap();
let echo_elf = elf::Elf::parse(echo_bytes).unwrap();
let echo_as = sys_as_create().unwrap();
let echo_entry = load_elf(&echo_elf, echo_as);
map_stack(echo_as, STACK_TOP, STACK_PAGES);
let _ = sys_task_create(echo_as, echo_entry, STACK_TOP).unwrap();
let client_bytes = cpio::find_file(INITRAMFS_START as *const u8, "client.elf").unwrap();
let client_elf = elf::Elf::parse(client_bytes).unwrap();
let client_as = sys_as_create().unwrap();
let client_entry = load_elf(&client_elf, client_as);
map_stack(client_as, STACK_TOP, STACK_PAGES);
let _ = sys_task_create(client_as, client_entry, STACK_TOP).unwrap();
sys_exit(0);
}
fn load_elf(elf: &elf::Elf, target_as: Handle) -> usize {
for header in elf.program_headers().unwrap() {
let header = header.unwrap();
if header.segment_type != elf::ProgramHeaderType::Load || header.memory_size == 0 {
continue;
}
let mut perms = 0;
if header.flags & 2 != 0 {
perms |= 1 << 0;
}
if header.flags & 1 != 0 {
perms |= 1 << 1;
}
let segment_start = header.virtual_address as usize;
let segment_end = segment_start + header.memory_size as usize;
let file_end = segment_start + header.file_size as usize;
let page_start = segment_start & !0xFFF;
for page in (page_start..segment_end).step_by(0x1000) {
let frame = sys_frame_alloc().unwrap();
sys_map(SELF_AS, frame, SCRATCH_PAGE, 0b01).unwrap();
unsafe {
core::ptr::write_bytes(SCRATCH_PAGE as *mut u8, 0, 0x1000);
let copy_start = page.max(segment_start).min(page + 0x1000);
let copy_end = (page + 0x1000).min(file_end).max(copy_start);
if copy_end > copy_start {
let page_offset = copy_start - page;
let file_offset = header.file_offset as usize + (copy_start - segment_start);
let len = copy_end - copy_start;
core::ptr::copy_nonoverlapping(
elf.bytes().as_ptr().add(file_offset),
(SCRATCH_PAGE as *mut u8).add(page_offset),
len,
);
}
}
sys_unmap(SELF_AS, SCRATCH_PAGE).unwrap();
sys_map(target_as, frame, page, perms).unwrap();
}
}
elf.entry()
}
fn map_stack(target_as: Handle, stack_top: usize, pages: usize) {
for i in 1..=pages {
let frame = sys_frame_alloc().unwrap();
let page_addr = stack_top - i * 0x1000;
sys_map(target_as, frame, page_addr, 0b01).unwrap();
}
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{info}");