Make Doom run by improving and fixing libc stubs, adding proper heap

allocation, adding a timer, a framebuffer swap, rectangle and buffer
draw syscall, moving the header files to the correct directory,
disabling stack protection, AVX and MMX, while enabling SSE, fix
interrupt handler popping in the wrong order, improve
load_segment_to_memory alignment, add load_from_ptr to framebuffer for
very quick drawing, serial_print as well as render font in
consolewriter, zero the memory in sbrk, add a fake file system and
methods to libxunil for DOOM, add correct printf, vsnprintf add _start
which calls the main function in libxunil, mark all registers as used
inside syscalls for no memory corruption, add build_helloworld.sh script
This commit is contained in:
csd4ni3l
2026-04-07 18:06:52 +02:00
parent ae3915147a
commit 9e8090c736
29 changed files with 980 additions and 256 deletions

View File

@@ -1,8 +1,9 @@
#[cfg(target_arch = "x86_64")]
pub use crate::arch::x86_64::paging::FRAME_ALLOCATOR_X86_64 as FRAME_ALLOCATOR;
use crate::driver::timer::TIMER;
use core::{alloc::GlobalAlloc, arch::asm};
use crate::{driver::timer::TIMER, util::serial_print};
use alloc::string::ToString;
use core::{alloc::GlobalAlloc, arch::asm, sync::atomic::Ordering};
use limine::response::{HhdmResponse, MemoryMapResponse};
#[cfg(target_arch = "x86_64")]
@@ -50,10 +51,11 @@ pub fn idle() {
}
pub fn sleep(ticks: u64) {
let start = TIMER.now();
while (TIMER.now() - start).elapsed() <= ticks {
idle();
}
// let start = TIMER.now();
// while start.ticks_since() < ticks {
// serial_print(start.ticks_since().to_string().as_str());
// core::hint::spin_loop();
// }
}
pub fn infinite_idle() -> ! {

View File

@@ -16,7 +16,7 @@ use crate::{
pub fn run_elf_x86_64(entry_point: *const u8, heap_base: u64) {
let stack_base: u64 = 0x0000_7fff_0000_0000;
let page_count = 3;
let page_count = 4096; // 16 mib
let page_size = 0x1000u64;
let stack_top = stack_base + (page_count as u64 * page_size);

View File

@@ -20,7 +20,7 @@ fn align_up(addr: usize, align: usize) -> usize {
pub static ALLOCATOR: Locked<LinkedListAllocator> = Locked::new(LinkedListAllocator::new());
pub const HEAP_START: usize = 0x_4444_4444_0000;
pub const HEAP_SIZE: usize = 256 * 1024 * 1024; // 256 MiB
pub const HEAP_SIZE: usize = 64 * 1024 * 1024; // 64 MiB
pub struct LinkedNode {
pub size: usize,
@@ -54,7 +54,7 @@ impl LinkedListAllocator {
fn size_align(layout: Layout) -> (usize, usize) {
let layout = layout
.align_to(core::mem::align_of::<LinkedNode>())
.align_to(16)
.expect("Align to LinkedNode failed")
.pad_to_align();
@@ -70,7 +70,7 @@ impl LinkedListAllocator {
}
unsafe fn add_free_memory_region(&mut self, start: usize, size: usize) {
assert_eq!(align_up(start, core::mem::align_of::<LinkedNode>()), start); // Check if we are up at least 1 LinkedNode size
assert_eq!(align_up(start, 16), start); // Check if we are up at least 1 LinkedNode size
assert!(size >= core::mem::size_of::<LinkedNode>()); // check if we have enough space for a LinkedNode
let mut linked_node = LinkedNode::new(size);

View File

@@ -8,8 +8,14 @@ use crate::{
util::serial_print,
};
use limine::response::{HhdmResponse, MemoryMapResponse};
use x86_64::instructions::interrupts::without_interrupts;
use x86_64::instructions::{interrupts, port::Port};
use x86_64::{
instructions::interrupts::without_interrupts,
registers::control::{Cr0, Cr0Flags},
};
use x86_64::{
instructions::{interrupts, port::Port},
registers::control::{Cr4, Cr4Flags},
};
const TIMER_PRECISION_HZ: u32 = 1000;
const PIT_DIVISOR: u16 = (1_193_182_u32 / TIMER_PRECISION_HZ) as u16;
@@ -53,6 +59,19 @@ pub fn init_x86_64<'a>(
memory_map_response: &'a MemoryMapResponse,
) -> OffsetPageTable<'static> {
load_gdt_x86_64();
unsafe {
let mut cr0 = Cr0::read();
cr0.remove(Cr0Flags::EMULATE_COPROCESSOR);
cr0.insert(Cr0Flags::MONITOR_COPROCESSOR);
Cr0::write(cr0);
let mut cr4 = Cr4::read();
cr4.insert(Cr4Flags::OSFXSR);
cr4.insert(Cr4Flags::OSXMMEXCPT_ENABLE);
Cr4::write(cr4);
}
init_idt_x86_64();
unsafe {

View File

@@ -6,6 +6,7 @@ use crate::{
timer::TIMER,
},
println,
util::serial_print,
};
use lazy_static::lazy_static;
use pc_keyboard::DecodedKey;
@@ -182,11 +183,11 @@ unsafe extern "C" fn syscall_interrupt_handler() {
"add rsp, 8",
"add rsp, 8",
// pop them in reverse orser
"pop rbx",
"pop rcx",
"pop rdx",
"pop rsi",
"pop rdi",
"pop rsi",
"pop rdx",
"pop rcx",
"pop rbx",
"pop rbp",
"pop r8",
"pop r9",

View File

@@ -291,27 +291,26 @@ pub fn load_segment_to_memory(
let p_offset: u64 = unsafe { (*phdr).p_offset };
let file_size: u64 = unsafe { (*phdr).p_filesz };
let vaddr: *mut u8 = get_vaddr(phdr, load_bias);
if p_offset > elf_bytes.len() as u64
|| file_size > elf_bytes.len() as u64
|| p_offset + file_size > elf_bytes.len() as u64
|| file_size > mem_size
{
return;
} // invalid, could read past it's memory
if file_size > mem_size {
return;
}
let vaddr: u64 = get_vaddr(phdr, load_bias) as u64;
let mem_page: u64 = align_down(vaddr, PAGE_SIZE);
let file_page: u64 = align_down(p_offset, PAGE_SIZE);
let page_off: u64 = vaddr - mem_page;
let seg_start = align_down(vaddr as u64, PAGE_SIZE);
let seg_end = align_up(vaddr as u64 + mem_size, PAGE_SIZE);
let seg_start = mem_page;
let seg_end = align_up(vaddr + mem_size, PAGE_SIZE);
let mut flags =
PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE | PageTableFlags::WRITABLE;
if unsafe { ((*phdr).p_flags & PF_X) != 0 } {
} else {
if unsafe { ((*phdr).p_flags & PF_X) == 0 } {
flags |= PageTableFlags::NO_EXECUTE;
}
@@ -337,19 +336,18 @@ pub fn load_segment_to_memory(
drop(frame_allocator);
let dst = (mem_page + page_off) as *mut u8;
let src = unsafe { elf_bytes.as_ptr().add(p_offset as usize) };
unsafe {
core::ptr::copy_nonoverlapping(
elf_bytes.as_ptr().add(p_offset as usize),
vaddr,
file_size as usize,
);
core::ptr::copy_nonoverlapping(src, dst, file_size as usize);
if mem_size > file_size {
memset(
vaddr.add(file_size as usize),
dst.add(file_size as usize),
0,
(mem_size - file_size) as usize,
);
}
};
}
}

View File

@@ -70,6 +70,22 @@ impl Framebuffer {
}
}
pub unsafe fn load_from_ptr(
&mut self,
src_ptr: *const u32,
src_width: usize,
src_height: usize,
) {
let h = core::cmp::min(src_height, self.height);
let w = core::cmp::min(src_width, self.width);
for y in 0..h {
let src_row = src_ptr.add(y * src_width);
let dst_row = self.back_buffer.as_mut_ptr().add(y * self.pitch);
core::ptr::copy_nonoverlapping(src_row, dst_row, w);
}
}
pub fn clear(&mut self, color: u32) {
self.back_buffer.fill(color);
}

View File

@@ -1,6 +1,6 @@
use crate::driver::graphics::base::rgb;
use crate::driver::graphics::font_render::render_text;
use crate::driver::graphics::framebuffer::Framebuffer;
use crate::{driver::graphics::base::rgb, util::serial_print};
use core::fmt::{self, Write};
use spin::Mutex;
@@ -15,6 +15,7 @@ pub struct ConsoleWriter<'a> {
impl Write for ConsoleWriter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
serial_print(s);
self.console.render_text(self.fb, s, 2, false);
Ok(())
}

View File

@@ -3,15 +3,19 @@ use core::{
ptr::null_mut,
};
use alloc::string::ToString;
use x86_64::{
VirtAddr,
structures::paging::{FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB},
};
use crate::{
arch::arch::{FRAME_ALLOCATOR, get_allocator, infinite_idle},
driver::graphics::framebuffer::with_framebuffer,
println,
arch::arch::{FRAME_ALLOCATOR, get_allocator, infinite_idle, sleep},
driver::{
graphics::{framebuffer::with_framebuffer, primitives::rectangle_filled},
timer::TIMER,
},
print, println,
task::scheduler::SCHEDULER,
util::{align_up, serial_print},
};
@@ -37,6 +41,10 @@ const UNLINK: usize = 87;
const GETDENTS64: usize = 217;
const CLOCK_GETTIME: usize = 228;
const EXIT_GROUP: usize = 231;
const SLEEP: usize = 909090; // zzz haha
const DRAW_PIXEL: usize = 5555;
const FRAMEBUFFER_SWAP: usize = 6666;
pub const DRAW_BUFFER: usize = 7777;
pub unsafe fn malloc(size: usize, align: usize) -> *mut u8 {
let align = if align < 1 {
@@ -74,7 +82,6 @@ pub unsafe fn memset(ptr: *mut u8, val: u8, count: usize) {
}
pub unsafe fn sbrk(increment: isize) -> isize {
serial_print("sbrk called");
let mut scheduler = SCHEDULER.lock();
if scheduler.current_process == -1 {
return -1;
@@ -100,34 +107,40 @@ pub unsafe fn sbrk(increment: isize) -> isize {
if new < heap_base {
return -1;
}
if new > stack_top - 3 * 4096 {
if new > stack_top - 16384 * 4096 {
// 67 mib max
return -1;
}
if new > old {
let map_start = align_up(old, 4096);
let map_end = align_up(new, 4096);
for addr in (map_start..map_end).step_by(4096) {
let frame = frame_allocator.allocate_frame().unwrap();
if let Some(frame) = frame_allocator.allocate_frame() {
// TODO: do not use x86_64 only
let virt_addr = VirtAddr::new(addr);
let page = Page::<Size4KiB>::containing_address(virt_addr);
unsafe {
process
.address_space
.mapper
.map_to(
page,
frame,
PageTableFlags::PRESENT
| PageTableFlags::WRITABLE
| PageTableFlags::USER_ACCESSIBLE
| PageTableFlags::NO_EXECUTE,
&mut *frame_allocator,
)
.unwrap()
.flush();
// TODO: do not use x86_64 only
let virt_addr = VirtAddr::new(addr);
let page = Page::<Size4KiB>::containing_address(virt_addr);
unsafe {
process
.address_space
.mapper
.map_to(
page,
frame,
PageTableFlags::PRESENT
| PageTableFlags::WRITABLE
| PageTableFlags::USER_ACCESSIBLE
| PageTableFlags::NO_EXECUTE,
&mut *frame_allocator,
)
.unwrap()
.flush();
core::ptr::write_bytes(virt_addr.as_mut_ptr::<u8>(), 0, 4096);
}
} else {
return -1;
}
}
}
@@ -135,8 +148,6 @@ pub unsafe fn sbrk(increment: isize) -> isize {
process.heap_end = new;
serial_print("sbrk finished");
return old as isize;
})
.unwrap_or(-1);
@@ -150,21 +161,58 @@ pub unsafe extern "C" fn syscall_dispatch(
arg2: isize,
) -> isize {
match num {
SYS_BRK => sbrk(arg0),
SYS_WRITE => {
BRK => sbrk(arg0),
WRITE => {
let buf_ptr = arg1 as *const u8;
let len = arg2 as usize;
let bytes: &[u8] = unsafe { core::slice::from_raw_parts(buf_ptr, len) };
let s = core::str::from_utf8(bytes).unwrap_or("<non-utf8>");
println!("SYS_WRITE called: {:?} {:?}", s, len);
if let Ok(s) = core::str::from_utf8(bytes) {
print!("{}", s);
} else {
for byte in bytes {
if *byte == b'\0' {
continue;
}
print!("{}", *byte as char);
}
}
with_framebuffer(|fb| fb.swap());
0
}
SYS_EXIT => {
println!("Program exit.");
EXIT => {
println!("Program exit: {}", arg0);
with_framebuffer(|fb| fb.swap());
infinite_idle();
}
67 => {
println!("{:?}", arg1);
0
}
SLEEP => {
sleep(arg0 as u64);
0
}
CLOCK_GETTIME => TIMER.now().elapsed() as isize,
DRAW_PIXEL => {
with_framebuffer(|mut fb| {
rectangle_filled(&mut fb, arg0 as usize, arg1 as usize, 4, 4, arg2 as u32);
});
0
}
DRAW_BUFFER => {
with_framebuffer(|mut fb| {
fb.load_from_ptr(arg0 as *const u32, arg1 as usize, arg2 as usize);
fb.swap();
});
0
}
FRAMEBUFFER_SWAP => {
with_framebuffer(|fb| {
fb.swap();
});
0
}
_ => -38, // syscall not found
}
}

View File

@@ -3,6 +3,8 @@ use core::{
sync::atomic::{AtomicU64, Ordering},
};
use crate::util::serial_print;
pub static TIMER: Timer = Timer::new();
pub struct Timer {
@@ -50,6 +52,11 @@ impl Time {
pub fn elapsed(&self) -> u64 {
self.interrupt_count
}
pub fn ticks_since(&self) -> u64 {
let now = TIMER.interrupt_count.load(Ordering::Relaxed);
now.saturating_sub(self.interrupt_count)
}
}
impl Add for Time {

View File

@@ -39,6 +39,8 @@ impl Locked<Scheduler> {
let stack_top = guard.processes[&pid].stack_top;
guard.current_process = pid as i64;
drop(guard);
enter_usermode(entry_point as u64, (stack_top & !0xF) - 8);
}

View File

@@ -26,7 +26,7 @@ use crate::{
static CURSOR_BYTES: &[u8] = include_bytes!("../../assets/cursors/default.bmp");
#[repr(C, align(8))]
#[repr(C, align(16))]
struct AlignedElf([u8; include_bytes!("../../assets/doomgeneric").len()]);
static TEST_ELF: AlignedElf = AlignedElf(*include_bytes!("../../assets/doomgeneric"));
static TEST_ELF_BYTES: &[u8] = &TEST_ELF.0;
@@ -86,8 +86,6 @@ fn boot_animation() {
pub fn userspace_init(mapper: &mut OffsetPageTable) -> ! {
// this is just a stub
boot_animation();
let (entry_point, heap_base) = load_file(mapper, TEST_ELF_BYTES);
println!("Entry point: {:?}", entry_point);