feat: elf loading

This commit is contained in:
Zoe
2026-09-05 10:12:42 -05:00
parent b0f804e412
commit dd596d5378
21 changed files with 1000 additions and 313 deletions
Generated
+4
View File
@@ -9,6 +9,10 @@ dependencies = [
"limine",
]
[[package]]
name = "init"
version = "0.1.0"
[[package]]
name = "limine"
version = "0.6.5"
+3
View File
@@ -3,6 +3,9 @@ name = "dusk"
version = "0.1.0"
edition = "2024"
[workspace]
members = [".", "userspace/init"]
[dependencies]
limine = "0.6.5"
+15 -11
View File
@@ -6,17 +6,17 @@ MEMORY ?= 512M
# In MB
ISO_SIZE ?= 512
QEMU_OPTS ?=
#MKSQUASHFS_OPTS ?=
GDB ?=
CPUS ?= 1
# FAT type
ESP_BITS ?= 32
EXPORT_SYMBOLS = true
#EXPORT_SYMBOLS = true
ISO_PATH = ${ARTIFACTS_PATH}/iso_root
#INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs
INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs
IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME}
ESP_IMAGE = ${ARTIFACTS_PATH}/esp.img
USERSPACE_CARGO_OPTS = --target ${ARCH}-unknown-none
CARGO_OPTS = -Zjson-target-spec --target=src/arch/${ARCH}/${ARCH}-unknown-none.json
QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH}
LIMINE_BOOT_VARIATION = X64
@@ -27,6 +27,7 @@ KERNEL_FILE = target/${ARCH}-unknown-none/${MODE}/dusk.elf
ifeq (${MODE},release)
CARGO_OPTS += --release
USERSPACE_CARGO_OPTS += --release
endif
ifneq (${CPUS},1)
@@ -50,7 +51,7 @@ endif
all: build
build: prepare-bin-files compile-bootloader compile-binaries run-scripts build-iso
build: prepare-bin-files compile-bootloader compile-binaries compile-initramfs build-iso
check:
cargo check -Zjson-target-spec
@@ -63,13 +64,16 @@ prepare-bin-files:
# Make bin/ and bin/iso_root
mkdir -p ${ARTIFACTS_PATH}
mkdir -p ${ISO_PATH}
# mkdir -p ${INITRAMFS_PATH}
run-scripts:
# Place the build ID into the binary so it can be read at runtime
@HASH=$$(md5sum ${KERNEL_FILE} | cut -c1-12) && \
sed -i "s/__BUILD_ID__/$${HASH}/" ${KERNEL_FILE}
mkdir -p ${INITRAMFS_PATH}
#python scripts/initramfs-test.py 100 ${INITRAMFS_PATH}/
compile-user:
RUSTFLAGS="-C relocation-model=static" cargo build --package init ${USERSPACE_CARGO_OPTS}
copy-initramfs-files: compile-user
cp -v target/${ARCH}-unknown-none/${MODE}/init ${INITRAMFS_PATH}/init.elf
compile-initramfs: copy-initramfs-files
(cd ${INITRAMFS_PATH} && find . -mindepth 1 | cpio -o -H newc) > ${ARTIFACTS_PATH}/initramfs.img
copy-iso-files:
# Limine files
@@ -81,7 +85,7 @@ copy-iso-files:
# OS files
cp -v ${KERNEL_FILE} ${ISO_PATH}/boot
#cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot
cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot
build-esp: copy-iso-files
# Create and populate formatted FAT image for ESP partition (130048 1K-blocks = ~127MiB)
+1
View File
@@ -4,4 +4,5 @@ timeout: 0
protocol: limine
path: boot():/boot/dusk.elf
module_path: boot():/boot/initramfs.img
-22
View File
@@ -183,28 +183,6 @@ impl LocalApic {
self.access.write(APIC_TIMER_INITIAL_COUNT, 0);
}
// pub fn delay_ticks(&self, count: u32) {
// if count == 0 {
// return;
// }
// APIC_TIMER_COUNT.store(0, Ordering::SeqCst);
// self.access
// .write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
// self.access.write(APIC_TIMER_DIVIDE_CONFIG, 0b11);
// self.access.write(APIC_TIMER_INITIAL_COUNT, count as u64);
// self.access.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64);
// while APIC_TIMER_COUNT.load(Ordering::SeqCst) == 0 {
// unsafe {
// core::arch::asm!("sti", "hlt", "cli", options(nomem, nostack));
// }
// }
// self.stop_timer();
// }
pub fn id(&self) -> u32 {
self.id
}
-31
View File
@@ -132,37 +132,6 @@ pub fn init_interrupt_controller(
})
}
// #[derive(Debug)]
// pub enum TimerError {
// DurationOverflow,
// }
// impl InterruptController {
// pub fn delay(&self, duration: core::time::Duration) -> Result<(), TimerError> {
// let nanoseconds = duration.as_nanos();
// let ticks = (nanoseconds
// .checked_mul(self.local_timer_frequency as u128)
// .ok_or(TimerError::DurationOverflow)?
// + 999_999_999)
// / 1_000_000_000;
// if ticks == 0 {
// return Ok(());
// }
// let mut remaining = ticks;
// while remaining > 0 {
// let chunk = remaining.min(u32::MAX as u128) as u32;
// self.local_apic.delay_ticks(chunk);
// remaining -= chunk as u128;
// }
// Ok(())
// }
// }
/// # Safety
///
/// The caller must ensure:
+14 -41
View File
@@ -390,16 +390,6 @@ impl PageTable {
.ok_or(UnmapError::PageNotMapped)
}
fn discard_private_tables(
frames: &mut [Option<OwnedFrame>; MAX_INTERMEDIATE_LEVELS],
count: usize,
allocator: &mut FrameAllocator,
) {
for frame in frames[..count].iter_mut().rev().filter_map(Option::take) {
unsafe { allocator.dealloc(frame) };
}
}
fn apply_user_upgrades(
&mut self,
upgrades: &[Option<EntryLocation>; MAX_INTERMEDIATE_LEVELS],
@@ -509,34 +499,18 @@ impl PageTable {
let private_table_count = levels.len() - missing_depth;
let mut private_tables: [Option<OwnedFrame>; MAX_INTERMEDIATE_LEVELS] =
core::array::from_fn(|_| None);
let mut allocated_count = 0;
while allocated_count < private_table_count {
let private_frame = match allocator.alloc() {
Some(frame) => frame,
None => {
Self::discard_private_tables(&mut private_tables, allocated_count, allocator);
return Err(MapError::OutOfFrames);
}
};
if PageTableEntry::new_table(
private_frame.frame_address(),
permissions.user_accessible,
self.config,
)
.is_err()
{
unsafe { allocator.dealloc(private_frame) };
Self::discard_private_tables(&mut private_tables, allocated_count, allocator);
return Err(MapError::PhysicalAddressTooLarge);
}
private_tables[allocated_count] = Some(private_frame);
allocated_count += 1;
}
let prepare_result = (|| {
for slot in &mut private_tables[..private_table_count] {
let frame = allocator.alloc().ok_or(MapError::OutOfFrames)?;
let address = frame.frame_address();
// Track ownership before validation so every error uses the same cleanup.
*slot = Some(frame);
PageTableEntry::new_table(address, permissions.user_accessible, self.config)
.map_err(|_| MapError::PhysicalAddressTooLarge)?;
}
for private_index in 0..private_table_count {
let private_frame = private_tables[private_index]
.as_ref()
@@ -572,7 +546,9 @@ impl PageTable {
let publication_entry = match prepare_result {
Ok(entry) => entry,
Err(error) => {
Self::discard_private_tables(&mut private_tables, allocated_count, allocator);
for frame in private_tables.into_iter().rev().flatten() {
unsafe { allocator.dealloc(frame) };
}
return Err(error);
}
};
@@ -583,10 +559,7 @@ impl PageTable {
[publication_location.index] = publication_entry;
// The published page table now owns these frames.
for frame in private_tables[..allocated_count]
.iter_mut()
.flat_map(Option::take)
{
for frame in private_tables.into_iter().flatten() {
let _ = frame.into_raw();
}
+5 -9
View File
@@ -13,15 +13,11 @@ const BINARY: u8 = 0;
pub const PIT_FREQUENCY: u64 = 1_193_182;
pub const PIT_CALIBRATION_COUNT: u16 = u16::MAX;
pub struct Pit;
pub fn start_pit_one_shot(count: u16) {
unsafe {
write_u8(PIT_COMMAND, CHANNEL_0 | LOW_HIGH | MODE_0 | BINARY);
impl Pit {
pub fn start_one_shot(count: u16) {
unsafe {
write_u8(PIT_COMMAND, CHANNEL_0 | LOW_HIGH | MODE_0 | BINARY);
write_u8(PIT_CHANNEL_0, count as u8);
write_u8(PIT_CHANNEL_0, (count >> 8) as u8);
}
write_u8(PIT_CHANNEL_0, count as u8);
write_u8(PIT_CHANNEL_0, (count >> 8) as u8);
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ use crate::{
io_apic::IoApic,
x86_64::{
interrupts::enable_interrupts,
pit::{PIT_CALIBRATION_COUNT, PIT_FREQUENCY, Pit},
pit::{PIT_CALIBRATION_COUNT, PIT_FREQUENCY, start_pit_one_shot},
},
},
platform::acpi::IsaIrqRoute,
@@ -37,7 +37,7 @@ pub fn calibrate_local_apic(
.map_err(|_| TimerCalibrationError::IoApicNotHandled)?;
local_apic.start_calibration_counter();
Pit::start_one_shot(PIT_CALIBRATION_COUNT);
start_pit_one_shot(PIT_CALIBRATION_COUNT);
enable_interrupts();
+40 -5
View File
@@ -1,13 +1,13 @@
use ::limine as limine_api;
use limine::paging::PagingMode;
use limine::request::{PagingModeRequest, RsdpRequest};
use limine::request::{ExecutableCmdlineRequest, ModulesRequest, PagingModeRequest, RsdpRequest};
use limine_api::request::{ExecutableAddressRequest, HhdmRequest, MemmapRequest};
use limine_api::{BaseRevision, RequestsEndMarker, RequestsStartMarker};
use crate::memory::{
KernelMemoryLayout, KernelSegment, MemoryMap, MemoryRegion, MemoryRegionKind, PagePermissions,
PhysicalAddr, VirtualAddr,
BootString, InitramfsImage, KernelMemoryLayout, KernelSegment, MemoryMap, MemoryRegion,
MemoryRegionKind, PagePermissions, PhysicalAddr, VirtualAddr,
};
/// Sets the base revision to the latest revision supported by the crate.
@@ -22,6 +22,14 @@ static BASE_REVISION: BaseRevision = BaseRevision::new();
#[unsafe(link_section = ".requests")]
static KERNEL_ADDRESS_REQUEST: ExecutableAddressRequest = ExecutableAddressRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static KERNEL_CMDLINE_REQUEST: ExecutableCmdlineRequest = ExecutableCmdlineRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static KERNEL_MODULE_REQUEST: ModulesRequest = ModulesRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
@@ -58,8 +66,12 @@ unsafe extern "C" {
static __data_end: u64;
}
const MAX_COMMAND_LINE_LENGTH: usize = 512;
pub struct BootInfo {
pub kernel_layout: KernelMemoryLayout,
pub command_line: BootString<MAX_COMMAND_LINE_LENGTH>,
pub initramfs: InitramfsImage,
pub hhdm_offset: usize,
pub memory_map: MemoryMap,
pub rsdp: VirtualAddr,
@@ -75,6 +87,9 @@ impl BootInfo {
pub enum BootError {
UnsupportedBaseRevision,
FailedToGetKernelAddress,
FailedToGetKernelCmdline,
FailedToGetModules,
FailedToGetInitramfs,
FailedToGetHHDMAddress,
FailedToGetMemmap,
TooManyMemoryRegions,
@@ -90,10 +105,28 @@ pub fn load_boot_info() -> Result<BootInfo, BootError> {
let kernel_address = KERNEL_ADDRESS_REQUEST
.response()
.ok_or(BootError::FailedToGetKernelAddress)?;
let kernel_cmdline = KERNEL_CMDLINE_REQUEST
.response()
.ok_or(BootError::FailedToGetKernelCmdline)?;
let command_line = BootString::from_bytes(kernel_cmdline.cmdline().as_bytes());
let modules = KERNEL_MODULE_REQUEST
.response()
.ok_or(BootError::FailedToGetModules)?;
let initramfs = modules
.modules()
.get(0)
.map(|module| {
let start = VirtualAddr::new(module.data().as_ptr() as usize);
let length = module.data().len();
InitramfsImage { start, length }
})
.ok_or(BootError::FailedToGetInitramfs)?;
let hhdm_offset = HHDM_REQUEST
.response()
.ok_or(BootError::FailedToGetHHDMAddress)?
.offset;
.offset as usize;
let rsdp = RSDP_REQUEST.response().ok_or(BootError::FailedToGetRsdp)?;
@@ -204,7 +237,9 @@ pub fn load_boot_info() -> Result<BootInfo, BootError> {
kernel_data_segment,
],
},
hhdm_offset: hhdm_offset as usize,
command_line,
initramfs,
hhdm_offset,
memory_map,
rsdp: VirtualAddr::new(rsdp.address as usize),
})
+76
View File
@@ -0,0 +1,76 @@
// CPIO newc
#[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: &'a [u8], target: &str) -> Option<&'a [u8]> {
let mut offset = 0;
while offset + core::mem::size_of::<Header>() <= archive.len() {
let header = Header::from_bytes(&archive[offset..])?;
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()?;
if offset + name_len > archive.len() {
return None;
}
let name_bytes = &archive[offset..offset + name_len];
let name = core::str::from_utf8(name_bytes)
.ok()?
.trim_end_matches('\0');
if name == "TRAILER!!!" {
return None;
}
let data_start = header_start + ((core::mem::size_of::<Header>() + name_len + 3) & !3);
if data_start + file_len > archive.len() {
return None;
}
if name == target {
return Some(&archive[data_start..data_start + file_len]);
}
offset = data_start + ((file_len + 3) & !3);
}
None
}
+423
View File
@@ -0,0 +1,423 @@
#[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)]
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
}
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(),
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod cpio;
pub mod elf;
+24 -187
View File
@@ -6,6 +6,7 @@
mod arch;
mod boot;
mod debug;
mod format;
mod memory;
mod platform;
mod syscall;
@@ -15,10 +16,7 @@ use core::arch::global_asm;
use crate::{
debug::serial,
memory::{
AddressSpace, DirectMap, FrameAllocator, KernelStackPool, MemoryRegionKind,
PagePermissions, UserStack, VirtualAddr,
},
memory::{AddressSpace, KernelStackPool, MemoryRegionKind, UserStack},
task::tcb::Tcb,
};
@@ -92,112 +90,6 @@ pub extern "C" fn _start() -> ! {
}
}
unsafe extern "C" {
static task_a_start: u8;
static task_a_end: u8;
static task_b_start: u8;
static task_b_end: u8;
static task_c_start: u8;
static task_c_end: u8;
}
unsafe fn embedded_code(start: *const u8, end: *const u8) -> &'static [u8] {
let length = unsafe { end.offset_from(start) as usize };
unsafe { core::slice::from_raw_parts(start, length) }
}
global_asm!(
r#"
.global task_a_start
task_a_start:
mov r12d, 100
.Ltask_a_loop:
mov eax, 3
mov edi, 1
lea rsi, [rip + .Ltask_a_message]
mov edx, 7
xor r10d, r10d
syscall
mov eax, 1
syscall
dec r12d
jnz .Ltask_a_loop
.Ltask_a_done:
mov eax, 2
syscall
jmp .Ltask_a_done
.Ltask_a_message:
.ascii "task A\n"
.global task_a_end
task_a_end:
.global task_b_start
task_b_start:
mov r12d, 100
.Ltask_b_loop:
mov eax, 3
mov edi, 1
lea rsi, [rip + .Ltask_b_message]
mov edx, 7
xor r10d, r10d
syscall
mov eax, 1
syscall
dec r12d
jnz .Ltask_b_loop
.Ltask_b_done:
mov eax, 2
syscall
jmp .Ltask_b_done
.Ltask_b_message:
.ascii "task B\n"
.global task_b_end
task_b_end:
.global task_c_start
task_c_start:
mov r12d, 100
.Ltask_c_loop:
mov eax, 3
mov edi, 1
lea rsi, [rip + .Ltask_c_message]
mov edx, 7
xor r10d, r10d
syscall
mov eax, 1
syscall
dec r12d
jnz .Ltask_c_loop
.Ltask_c_done:
mov eax, 2
syscall
jmp .Ltask_c_done
.Ltask_c_message:
.ascii "task C\n"
.global task_c_end
task_c_end:
"#
);
pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! {
let (
mut allocator,
@@ -227,6 +119,9 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! {
MemoryRegionKind::BootloaderReclaimable,
);
let init_code = format::cpio::find_file(boot_info.initramfs.data(), "init.elf")
.expect("Failed to load init program from initramfs");
println!("Initializing local ACPI...",);
let acpi = platform::acpi::init(&boot_info, direct_map).expect("failed to initialize ACPI");
@@ -244,95 +139,37 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! {
arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space)
.expect("failed to initialize interrupt controller");
let task_a_code = unsafe { embedded_code(&task_a_start, &task_a_end) };
let task_b_code = unsafe { embedded_code(&task_b_start, &task_b_end) };
let task_c_code = unsafe { embedded_code(&task_c_start, &task_c_end) };
let user_kernel_stack = kernel_stack_pool
.allocate(&mut address_space, &mut allocator)
.expect("failed to allocate task kernel stack");
let task_a = create_test_task(
task_a_code,
&mut address_space,
&mut kernel_stack_pool,
let mut user_address_space = address_space
.new_user(&mut allocator)
.expect("failed to create user address space");
let image = task::loader::load_elf(
init_code,
&mut user_address_space,
&mut allocator,
direct_map,
);
let task_b = create_test_task(
task_b_code,
&mut address_space,
&mut kernel_stack_pool,
&mut allocator,
direct_map,
let user_stack = UserStack::allocate(&mut user_address_space, &mut allocator)
.expect("failed to allocate user stack");
let task = Tcb::new_user(
0, // overwritten by add_task for now
user_address_space,
user_kernel_stack,
image.expect("Failed to load elf"),
user_stack.top(),
);
let task_c = create_test_task(
task_c_code,
&mut address_space,
&mut kernel_stack_pool,
&mut allocator,
direct_map,
);
task::scheduler::add_task(task_a).expect("scheduler is full");
task::scheduler::add_task(task_b).expect("scheduler is full");
task::scheduler::add_task(task_c).expect("scheduler is full");
task::scheduler::add_task(task).expect("scheduler is full");
task::scheduler::start();
hcf();
}
fn create_test_task(
code: &[u8],
kernel_address_space: &mut AddressSpace,
kernel_stack_pool: &mut KernelStackPool,
allocator: &mut FrameAllocator,
direct_map: DirectMap,
) -> Tcb {
assert!(code.len() <= memory::FRAME_SIZE);
let kernel_stack = kernel_stack_pool
.allocate(kernel_address_space, allocator)
.expect("failed to allocate task kernel stack");
let mut user_address_space = kernel_address_space
.new_user(allocator)
.expect("failed to create user address space");
let user_stack = UserStack::allocate(&mut user_address_space, allocator)
.expect("failed to allocate user stack");
let entry = VirtualAddr::new(0x8000);
let code_frame = allocator
.alloc()
.expect("failed to allocate code frame")
.into_raw();
user_address_space
.map(
code_frame.start_address(),
entry,
PagePermissions::new(false, true, true),
allocator,
memory::CachePolicy::WriteBack,
)
.expect("failed to map user code");
let destination = direct_map
.translate(code_frame.start_address())
.expect("code frame outside direct map");
unsafe {
core::ptr::copy_nonoverlapping(code.as_ptr(), destination.as_mut_ptr(), code.len());
}
Tcb::new_user(
0, // overwritten by add_task for now
user_address_space,
kernel_stack,
entry,
user_stack.top(),
)
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("Uh oh, something went wrong!");
+8 -3
View File
@@ -113,13 +113,18 @@ impl AddressSpace {
// undermind the permissions of the explicitly mapped kernel image
if matches!(
region.kind,
MemoryRegionKind::Reserved
| MemoryRegionKind::BadMemory
| MemoryRegionKind::KernelAndModules
MemoryRegionKind::Reserved | MemoryRegionKind::BadMemory
) {
continue;
}
// we shouldnt HHDM the kernel image, but we should map modules
if region.kind == MemoryRegionKind::KernelAndModules
&& region.start == layout.segments[0].physical_base
{
continue;
}
let cache_policy = if matches!(
region.kind,
MemoryRegionKind::MappedReserved | MemoryRegionKind::Framebuffer
+41
View File
@@ -3,6 +3,8 @@ mod frame;
mod stack;
mod user;
use core::ops::Add;
#[allow(unused)]
pub use address_space::{AddressSpace, AddressSpaceCreateError, MapError, UnmapError};
pub use frame::{FRAME_SIZE, FrameAddr, FrameAllocator, OwnedFrame};
@@ -11,6 +13,37 @@ pub use stack::{KernelStack, KernelStackPool, StackCreateError, UserStack};
#[allow(unused)]
pub use user::*;
pub struct BootString<const N: usize> {
bytes: [u8; N],
len: usize,
}
impl<const N: usize> BootString<N> {
pub fn from_bytes(bytes: &[u8]) -> Self {
let len = bytes.len();
let mut boot_string = Self { bytes: [0; N], len };
boot_string.bytes[..len].copy_from_slice(bytes);
boot_string
}
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.bytes[..self.len]).unwrap()
}
}
const MAX_MODULE_PATH_LENGTH: usize = 256;
pub struct InitramfsImage {
pub start: VirtualAddr,
pub length: usize,
}
impl InitramfsImage {
pub fn data(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.start.as_ptr(), self.length) }
}
}
pub struct KernelSegment {
pub physical_base: PhysicalAddr,
pub virtual_base: VirtualAddr,
@@ -75,6 +108,14 @@ impl VirtualAddr {
}
}
impl Add<usize> for VirtualAddr {
type Output = Self;
fn add(self, rhs: usize) -> Self::Output {
Self(self.0 + rhs)
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MemoryRegionKind {
+212
View File
@@ -0,0 +1,212 @@
use crate::{
format,
memory::{
self, AddressSpace, DirectMap, FRAME_SIZE, FrameAllocator, OwnedFrame, PagePermissions,
VirtualAddr,
},
println,
};
const MAX_LOAD_SEGMENTS: usize = 32;
#[derive(Debug)]
struct LoadedRegion {
start: VirtualAddr,
mapped_pages: usize,
}
#[derive(Debug)]
pub struct LoadedImage {
pub entry: VirtualAddr,
regions: [LoadedRegion; MAX_LOAD_SEGMENTS],
region_count: usize,
}
impl LoadedImage {
/// # Safety
/// The supplied address space must contain this image's original mappings.
/// Its frames must be exclusively owned by this image and no longer in use.
pub unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) {
for region in self.regions[..self.region_count].iter().rev() {
for page in (0..region.mapped_pages).rev() {
let address = VirtualAddr::new(region.start.as_usize() + page * FRAME_SIZE);
let frame = unsafe {
address_space
.unmap(address, allocator)
.expect("loaded image mapping was unexpectedly missing")
};
unsafe { allocator.dealloc(OwnedFrame::from_raw(frame)) };
}
}
}
}
#[derive(Debug)]
pub enum ElfLoadError {
AddressTranslationFailed,
FailedToMapSegment,
AddressOverflow,
InvalidStack,
OutOfMemory,
InvalidElf,
TooManyLoadSegments,
}
#[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
}
pub fn load_elf(
bytes: &[u8],
user_address_space: &mut AddressSpace,
allocator: &mut FrameAllocator,
direct_map: DirectMap,
) -> Result<LoadedImage, ElfLoadError> {
let program = format::elf::Elf::parse(bytes).map_err(|_| ElfLoadError::InvalidElf)?;
if !is_loadable(&program) {
return Err(ElfLoadError::InvalidElf);
}
let mut image = LoadedImage {
entry: VirtualAddr::new(program.entry()),
regions: core::array::from_fn(|_| LoadedRegion {
start: VirtualAddr::new(0),
mapped_pages: 0,
}),
region_count: 0,
};
let result = (|| {
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 => {
if image.region_count == MAX_LOAD_SEGMENTS {
return Err(ElfLoadError::TooManyLoadSegments);
}
// 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;
let region = &mut image.regions[image.region_count];
region.start = VirtualAddr::new(page_start);
image.region_count += 1;
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();
region.mapped_pages += 1;
}
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(())
})();
if let Err(error) = result {
// Only pages created by this load are recorded; none have been handed to a task.
unsafe { image.destroy(user_address_space, allocator) };
return Err(error);
}
Ok(image)
}
+1
View File
@@ -1,2 +1,3 @@
pub mod loader;
pub mod scheduler;
pub mod tcb;
+5 -2
View File
@@ -1,6 +1,7 @@
use crate::{
arch::ThreadContext,
memory::{AddressSpace, KernelStack, VirtualAddr},
task::loader::LoadedImage,
};
// Thread Control Block
@@ -26,6 +27,7 @@ pub struct Tcb {
pub kernel_stack: KernelStack,
pub context: ThreadContext,
pub address_space: AddressSpace,
pub image: LoadedImage,
}
impl Tcb {
@@ -33,10 +35,10 @@ impl Tcb {
id: usize,
address_space: AddressSpace,
kernel_stack: KernelStack,
user_entry: VirtualAddr,
image: LoadedImage,
user_stack: VirtualAddr,
) -> Self {
let context = ThreadContext::new(user_entry, user_stack, kernel_stack.top());
let context = ThreadContext::new(image.entry, user_stack, kernel_stack.top());
Self {
id,
@@ -44,6 +46,7 @@ impl Tcb {
kernel_stack,
context,
address_space,
image,
}
}
}
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "init"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "init"
test = false
bench = false
+115
View File
@@ -0,0 +1,115 @@
#![no_std]
#![no_main]
use core::arch::asm;
use core::cell::UnsafeCell;
use core::fmt::Write;
fn sys_yield() {
unsafe {
asm!("mov rax, 1", "syscall");
}
}
fn sys_write(fd: usize, buf: &str) -> Result<(), usize> {
unsafe {
let status;
asm!(
"mov rax, 3",
"syscall",
in("rdi") fd,
in("rsi") buf.as_ptr(),
in("rdx") buf.len(),
in("r10") 0,
lateout("rax") status,
);
if status != 0 { Err(status) } else { Ok(()) }
}
}
struct Writer;
impl core::fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
sys_write(1, s).map_err(|_| core::fmt::Error)?;
Ok(())
}
}
#[macro_export]
macro_rules! print {
($($arg:tt)*) => (let _ = $crate::Writer.write_fmt(format_args!($($arg)*)););
}
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
pub fn sys_exit(exit_code: usize) -> ! {
unsafe {
asm!(
"mov rax, 2",
"syscall",
in("rdi") exit_code,
options(noreturn)
);
}
}
struct Heap {
pub data: UnsafeCell<[u8; 1024]>,
}
impl Heap {
const fn new() -> Self {
Self {
data: UnsafeCell::new([0; 1024]),
}
}
fn len(&self) -> usize {
unsafe { (*self.data.get()).len() }
}
}
impl core::ops::Deref for Heap {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { (*self.data.get()).as_ref() }
}
}
unsafe impl Sync for Heap {}
static HEAP: Heap = Heap::new();
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
(0..100).for_each(|i| {
println!("Hello {}", i);
sys_yield();
});
let len = HEAP.len();
for i in 0..len {
unsafe { HEAP.data.get().as_mut().unwrap()[i] = i as u8 };
}
(0..1024).for_each(|i| {
println!("{}", unsafe { HEAP.data.get().as_ref().unwrap()[i] });
sys_yield();
});
sys_exit(0);
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{info}");
sys_exit(1);
}