diff --git a/Makefile b/Makefile index e0447cf..4fee11d 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,16 @@ NO_COLOR ?= 0 BUILD_ROOT := build/x86_64 OBJ_ROOT := $(BUILD_ROOT)/obj ISO_ROOT := $(BUILD_ROOT)/iso_root +RAMDISK_ROOT := $(BUILD_ROOT)/ramdisk_root KERNEL_ELF := $(BUILD_ROOT)/clks_kernel.elf RAMDISK_IMAGE := $(BUILD_ROOT)/cleonos_ramdisk.tar ISO_IMAGE := build/CLeonOS-x86_64.iso +USER_BUILD_ROOT := $(BUILD_ROOT)/user +USER_OBJ_ROOT := $(USER_BUILD_ROOT)/obj +USER_APP_DIR := $(USER_BUILD_ROOT)/apps +USER_LIB_DIR := $(USER_BUILD_ROOT)/lib + LIMINE_DIR ?= limine LIMINE_REPO ?= https://gh-proxy.com/https://github.com/limine-bootloader/limine.git LIMINE_REF ?= @@ -33,6 +39,13 @@ LINKER_SCRIPT := clks/arch/x86_64/linker.ld RUN_COMMAND := $(QEMU_X86_64) -M q35 -m 1024M -cdrom $(ISO_IMAGE) -serial stdio DEBUG_COMMAND := $(QEMU_X86_64) -M q35 -m 1024M -cdrom $(ISO_IMAGE) -serial stdio -s -S +USER_CC ?= cc +USER_LD ?= ld +RUSTC ?= rustc +USER_LINKER_SCRIPT := cleonos/c/user.ld +USER_CFLAGS := -std=c11 -ffreestanding -fno-stack-protector -fno-builtin -Wall -Wextra -Werror -Icleonos/c/include +USER_LDFLAGS := -nostdlib -z max-page-size=0x1000 -T $(USER_LINKER_SCRIPT) + ifeq ($(NO_COLOR),1) COLOR_RESET := COLOR_INFO := @@ -77,6 +90,7 @@ C_SOURCES := \ clks/kernel/syscall.c \ clks/kernel/ramdisk.c \ clks/kernel/fs.c \ + clks/kernel/userland.c \ clks/lib/string.c \ clks/drivers/serial/serial.c \ clks/drivers/video/framebuffer.c \ @@ -90,11 +104,22 @@ C_OBJECTS := $(patsubst %.c,$(OBJ_ROOT)/%.o,$(C_SOURCES)) ASM_OBJECTS := $(patsubst %.S,$(OBJ_ROOT)/%.o,$(ASM_SOURCES)) OBJECTS := $(C_OBJECTS) $(ASM_OBJECTS) +USER_COMMON_SOURCES := \ + cleonos/c/src/runtime.c \ + cleonos/c/src/syscall.c + +USER_COMMON_OBJECTS := $(patsubst %.c,$(USER_OBJ_ROOT)/%.o,$(USER_COMMON_SOURCES)) +USER_SHELL_OBJECT := $(USER_OBJ_ROOT)/cleonos/c/apps/shell_main.o +USER_ELFRUNNER_OBJECT := $(USER_OBJ_ROOT)/cleonos/c/apps/elfrunner_main.o +USER_MEMC_OBJECT := $(USER_OBJ_ROOT)/cleonos/c/apps/memc_main.o +USER_RUST_LIB := $(USER_LIB_DIR)/libcleonos_user_rust.a +USER_APPS := $(USER_APP_DIR)/shell.elf $(USER_APP_DIR)/elfrunner.elf $(USER_APP_DIR)/memc.elf + CFLAGS_COMMON := -std=c11 -ffreestanding -fno-stack-protector -fno-builtin -Wall -Wextra -Werror -Iclks/include ASFLAGS_COMMON := -ffreestanding -Iclks/include LDFLAGS_COMMON := -nostdlib -z max-page-size=0x1000 -.PHONY: all setup setup-tools setup-limine kernel ramdisk iso run debug clean clean-all help +.PHONY: all setup setup-tools setup-limine kernel userapps ramdisk-root ramdisk iso run debug clean clean-all help all: iso @@ -111,6 +136,9 @@ setup-tools: > @command -v $(OBJCOPY_FOR_TARGET) >/dev/null 2>&1 || (printf '%b\n' "$(COLOR_ERROR)[ERROR]$(COLOR_RESET) missing tool: $(OBJCOPY_FOR_TARGET)" && exit 1) > @command -v $(OBJDUMP_FOR_TARGET) >/dev/null 2>&1 || (printf '%b\n' "$(COLOR_ERROR)[ERROR]$(COLOR_RESET) missing tool: $(OBJDUMP_FOR_TARGET)" && exit 1) > @command -v $(READELF_FOR_TARGET) >/dev/null 2>&1 || (printf '%b\n' "$(COLOR_ERROR)[ERROR]$(COLOR_RESET) missing tool: $(READELF_FOR_TARGET)" && exit 1) +> @command -v $(USER_CC) >/dev/null 2>&1 || (printf '%b\n' "$(COLOR_ERROR)[ERROR]$(COLOR_RESET) missing tool: $(USER_CC)" && exit 1) +> @command -v $(USER_LD) >/dev/null 2>&1 || (printf '%b\n' "$(COLOR_ERROR)[ERROR]$(COLOR_RESET) missing tool: $(USER_LD)" && exit 1) +> @command -v $(RUSTC) >/dev/null 2>&1 || (printf '%b\n' "$(COLOR_ERROR)[ERROR]$(COLOR_RESET) missing tool: $(RUSTC)" && exit 1) > $(call log_info,required tools are available) setup-limine: @@ -166,6 +194,19 @@ setup-limine: kernel: $(KERNEL_ELF) +userapps: $(USER_APPS) +> $(call log_info,user elf apps ready) + +ramdisk-root: userapps +> $(call log_step,staging ramdisk root -> $(RAMDISK_ROOT)) +> @rm -rf $(RAMDISK_ROOT) +> @mkdir -p $(RAMDISK_ROOT) +> @cp -a ramdisk/. $(RAMDISK_ROOT)/ +> @mkdir -p $(RAMDISK_ROOT)/system $(RAMDISK_ROOT)/shell +> @cp $(USER_APP_DIR)/shell.elf $(RAMDISK_ROOT)/shell/shell.elf +> @cp $(USER_APP_DIR)/elfrunner.elf $(RAMDISK_ROOT)/system/elfrunner.elf +> @cp $(USER_APP_DIR)/memc.elf $(RAMDISK_ROOT)/system/memc.elf + ramdisk: $(RAMDISK_IMAGE) $(KERNEL_ELF): $(OBJECTS) $(LINKER_SCRIPT) Makefile @@ -183,11 +224,35 @@ $(OBJ_ROOT)/%.o: %.S Makefile > @mkdir -p $(dir $@) > @$(CC) $(ASFLAGS_COMMON) $(ARCH_CFLAGS) -c $< -o $@ +$(USER_OBJ_ROOT)/%.o: %.c Makefile +> $(call log_step,compiling user $<) +> @mkdir -p $(dir $@) +> @$(USER_CC) $(USER_CFLAGS) -c $< -o $@ -$(RAMDISK_IMAGE): +$(USER_RUST_LIB): cleonos/rust/src/lib.rs Makefile +> $(call log_step,building rust user lib) +> @mkdir -p $(dir $@) +> @$(RUSTC) --crate-type staticlib -C panic=abort -O $< -o $@ + +$(USER_APP_DIR)/shell.elf: $(USER_COMMON_OBJECTS) $(USER_SHELL_OBJECT) $(USER_RUST_LIB) $(USER_LINKER_SCRIPT) +> $(call log_step,linking user shell.elf) +> @mkdir -p $(dir $@) +> @$(USER_LD) $(USER_LDFLAGS) -o $@ $(USER_COMMON_OBJECTS) $(USER_SHELL_OBJECT) $(USER_RUST_LIB) + +$(USER_APP_DIR)/elfrunner.elf: $(USER_COMMON_OBJECTS) $(USER_ELFRUNNER_OBJECT) $(USER_LINKER_SCRIPT) +> $(call log_step,linking user elfrunner.elf) +> @mkdir -p $(dir $@) +> @$(USER_LD) $(USER_LDFLAGS) -o $@ $(USER_COMMON_OBJECTS) $(USER_ELFRUNNER_OBJECT) + +$(USER_APP_DIR)/memc.elf: $(USER_COMMON_OBJECTS) $(USER_MEMC_OBJECT) $(USER_LINKER_SCRIPT) +> $(call log_step,linking user memc.elf) +> @mkdir -p $(dir $@) +> @$(USER_LD) $(USER_LDFLAGS) -o $@ $(USER_COMMON_OBJECTS) $(USER_MEMC_OBJECT) + +$(RAMDISK_IMAGE): ramdisk-root Makefile > $(call log_step,packing ramdisk -> $(RAMDISK_IMAGE)) > @mkdir -p $(dir $@) -> @$(TAR) -C ramdisk -cf $@ . +> @$(TAR) -C $(RAMDISK_ROOT) -cf $@ . iso: setup-tools setup-limine $(KERNEL_ELF) $(RAMDISK_IMAGE) configs/limine.conf > $(call log_step,assembling iso root) @@ -241,8 +306,8 @@ help: > @echo " make setup LIMINE_SKIP_CONFIGURE=1" > @echo " make setup LIMINE_CONFIGURE_FLAGS='--enable-bios-cd --enable-uefi-cd --enable-uefi-x86-64'" > @echo " make setup OBJCOPY_FOR_TARGET=llvm-objcopy OBJDUMP_FOR_TARGET=llvm-objdump READELF_FOR_TARGET=llvm-readelf" +> @echo " make userapps" > @echo " make iso" > @echo " make run" > @echo " make debug" > @echo " make NO_COLOR=1 " - diff --git a/cleonos/c/apps/elfrunner_main.c b/cleonos/c/apps/elfrunner_main.c new file mode 100644 index 0000000..8070410 --- /dev/null +++ b/cleonos/c/apps/elfrunner_main.c @@ -0,0 +1,8 @@ +#include + +static const char elfrunner_banner[] = "[KAPP][ELFRUNNER] elfrunner.elf online"; + +int cleonos_app_main(void) { + cleonos_sys_log_write(elfrunner_banner, (u64)(sizeof(elfrunner_banner) - 1U)); + return 0; +} diff --git a/cleonos/c/apps/memc_main.c b/cleonos/c/apps/memc_main.c new file mode 100644 index 0000000..6a49c82 --- /dev/null +++ b/cleonos/c/apps/memc_main.c @@ -0,0 +1,10 @@ +#include + +static const char memc_banner[] = "[KAPP][MEMC] memc.elf online"; + +int cleonos_app_main(void) { + u64 ticks = cleonos_sys_timer_ticks(); + (void)ticks; + cleonos_sys_log_write(memc_banner, (u64)(sizeof(memc_banner) - 1U)); + return 0; +} diff --git a/cleonos/c/apps/shell_main.c b/cleonos/c/apps/shell_main.c new file mode 100644 index 0000000..7b14f17 --- /dev/null +++ b/cleonos/c/apps/shell_main.c @@ -0,0 +1,13 @@ +#include +#include + +static const char shell_banner[] = "[USER][SHELL] shell.elf online"; +static const char shell_status[] = "[USER][SHELL] syscall int80 path ok"; + +int cleonos_app_main(void) { + u64 len = cleonos_rust_guarded_len((const unsigned char *)shell_banner, (usize)(sizeof(shell_banner) - 1U)); + + cleonos_sys_log_write(shell_banner, len); + cleonos_sys_log_write(shell_status, (u64)(sizeof(shell_status) - 1U)); + return 0; +} diff --git a/cleonos/c/include/cleonos_rust_bridge.h b/cleonos/c/include/cleonos_rust_bridge.h new file mode 100644 index 0000000..b363677 --- /dev/null +++ b/cleonos/c/include/cleonos_rust_bridge.h @@ -0,0 +1,9 @@ +#ifndef CLEONOS_RUST_BRIDGE_H +#define CLEONOS_RUST_BRIDGE_H + +typedef unsigned long long u64; +typedef unsigned long long usize; + +u64 cleonos_rust_guarded_len(const unsigned char *ptr, usize max_len); + +#endif diff --git a/cleonos/c/include/cleonos_syscall.h b/cleonos/c/include/cleonos_syscall.h new file mode 100644 index 0000000..9602e06 --- /dev/null +++ b/cleonos/c/include/cleonos_syscall.h @@ -0,0 +1,16 @@ +#ifndef CLEONOS_SYSCALL_H +#define CLEONOS_SYSCALL_H + +typedef unsigned long long u64; +typedef unsigned long long usize; + +#define CLEONOS_SYSCALL_LOG_WRITE 0ULL +#define CLEONOS_SYSCALL_TIMER_TICKS 1ULL +#define CLEONOS_SYSCALL_TASK_COUNT 2ULL +#define CLEONOS_SYSCALL_CUR_TASK 3ULL + +u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2); +u64 cleonos_sys_log_write(const char *message, u64 length); +u64 cleonos_sys_timer_ticks(void); + +#endif diff --git a/cleonos/c/src/runtime.c b/cleonos/c/src/runtime.c new file mode 100644 index 0000000..861878e --- /dev/null +++ b/cleonos/c/src/runtime.c @@ -0,0 +1,16 @@ +#include + +typedef int (*cleonos_entry_fn)(void); + +extern int cleonos_app_main(void); + +void _start(void) { + volatile int code; + + code = ((cleonos_entry_fn)cleonos_app_main)(); + (void)code; + + for (;;) { + __asm__ volatile("pause"); + } +} diff --git a/cleonos/c/src/syscall.c b/cleonos/c/src/syscall.c new file mode 100644 index 0000000..1017bd0 --- /dev/null +++ b/cleonos/c/src/syscall.c @@ -0,0 +1,22 @@ +#include + +u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2) { + u64 ret; + + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(id), "b"(arg0), "c"(arg1), "d"(arg2) + : "memory" + ); + + return ret; +} + +u64 cleonos_sys_log_write(const char *message, u64 length) { + return cleonos_syscall(CLEONOS_SYSCALL_LOG_WRITE, (u64)message, length, 0ULL); +} + +u64 cleonos_sys_timer_ticks(void) { + return cleonos_syscall(CLEONOS_SYSCALL_TIMER_TICKS, 0ULL, 0ULL, 0ULL); +} diff --git a/cleonos/c/stub.c b/cleonos/c/stub.c index 50dd316..b94c3eb 100644 --- a/cleonos/c/stub.c +++ b/cleonos/c/stub.c @@ -1,8 +1,8 @@ /* - * CLeonOS user-space C toolchain stub. - * Stage1 only defines build skeleton. Real shell/user ELF apps start in later stages. + * CLeonOS user-space bootstrap placeholder kept for compatibility. + * Stage7 introduces real user ELF apps under cleonos/c/apps. */ int cleonos_c_stub(void) { return 0; -} \ No newline at end of file +} diff --git a/cleonos/c/user.ld b/cleonos/c/user.ld new file mode 100644 index 0000000..e1f0694 --- /dev/null +++ b/cleonos/c/user.ld @@ -0,0 +1,29 @@ +OUTPUT_FORMAT(elf64-x86-64) +ENTRY(_start) + +PHDRS { + text PT_LOAD FLAGS(5); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(6); +} + +SECTIONS { + . = 0x00400000; + + .text : ALIGN(0x1000) { + *(.text .text.*) + } :text + + .rodata : ALIGN(0x1000) { + *(.rodata .rodata.*) + } :rodata + + .data : ALIGN(0x1000) { + *(.data .data.*) + } :data + + .bss : ALIGN(0x1000) { + *(COMMON) + *(.bss .bss.*) + } :data +} diff --git a/cleonos/rust/src/lib.rs b/cleonos/rust/src/lib.rs index 6577300..c24ab84 100644 --- a/cleonos/rust/src/lib.rs +++ b/cleonos/rust/src/lib.rs @@ -1,6 +1,31 @@ #![no_std] +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop { + core::hint::spin_loop(); + } +} + #[no_mangle] -pub extern "C" fn cleonos_rust_stub() -> u64 { - 0 -} \ No newline at end of file +pub extern "C" fn cleonos_rust_guarded_len(ptr: *const u8, max_len: usize) -> u64 { + let mut i: usize = 0; + + if ptr.is_null() { + return 0; + } + + while i < max_len { + let ch = unsafe { *ptr.add(i) }; + + if ch == 0 { + break; + } + + i += 1; + } + + i as u64 +} diff --git a/clks/arch/x86_64/interrupt_stubs.S b/clks/arch/x86_64/interrupt_stubs.S index f23a0f1..e4d410f 100644 --- a/clks/arch/x86_64/interrupt_stubs.S +++ b/clks/arch/x86_64/interrupt_stubs.S @@ -115,4 +115,5 @@ clks_isr_common: pop r15 add rsp, 16 - iretq \ No newline at end of file + iretq +.section .note.GNU-stack,"",@progbits diff --git a/clks/include/clks/userland.h b/clks/include/clks/userland.h new file mode 100644 index 0000000..8cf949c --- /dev/null +++ b/clks/include/clks/userland.h @@ -0,0 +1,8 @@ +#ifndef CLKS_USERLAND_H +#define CLKS_USERLAND_H + +#include + +clks_bool clks_userland_init(void); + +#endif diff --git a/clks/kernel/kmain.c b/clks/kernel/kmain.c index 6eedfd3..9d7d949 100644 --- a/clks/kernel/kmain.c +++ b/clks/kernel/kmain.c @@ -13,6 +13,7 @@ #include #include #include +#include void clks_kernel_main(void) { const struct limine_framebuffer *boot_fb; @@ -39,7 +40,7 @@ void clks_kernel_main(void) { clks_tty_init(); } - clks_log(CLKS_LOG_INFO, "BOOT", "CLEONOS STAGE6 START"); + clks_log(CLKS_LOG_INFO, "BOOT", "CLEONOS STAGE7 START"); if (boot_fb == CLKS_NULL) { clks_log(CLKS_LOG_WARN, "VIDEO", "NO FRAMEBUFFER FROM LIMINE"); @@ -101,6 +102,11 @@ void clks_kernel_main(void) { clks_cpu_halt_forever(); } + if (clks_userland_init() == CLKS_FALSE) { + clks_log(CLKS_LOG_ERROR, "USER", "USERLAND INIT FAILED"); + clks_cpu_halt_forever(); + } + clks_scheduler_init(); if (clks_scheduler_add_kernel_task("klogd", 4U) == CLKS_FALSE) { @@ -133,4 +139,3 @@ void clks_kernel_main(void) { clks_cpu_halt_forever(); } - diff --git a/clks/kernel/userland.c b/clks/kernel/userland.c new file mode 100644 index 0000000..2368966 --- /dev/null +++ b/clks/kernel/userland.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include + +static clks_bool clks_userland_probe_elf(const char *path, const char *tag) { + const void *image; + u64 size = 0ULL; + struct clks_elf64_info info; + + image = clks_fs_read_all(path, &size); + + if (image == CLKS_NULL) { + clks_log(CLKS_LOG_ERROR, "USER", "ELF FILE MISSING"); + clks_log(CLKS_LOG_ERROR, "USER", path); + return CLKS_FALSE; + } + + if (clks_elf64_inspect(image, size, &info) == CLKS_FALSE) { + clks_log(CLKS_LOG_ERROR, "USER", "ELF INSPECT FAILED"); + clks_log(CLKS_LOG_ERROR, "USER", path); + return CLKS_FALSE; + } + + clks_log(CLKS_LOG_INFO, "USER", tag); + clks_log_hex(CLKS_LOG_INFO, "USER", "ELF_SIZE", size); + clks_log_hex(CLKS_LOG_INFO, "USER", "ENTRY", info.entry); + return CLKS_TRUE; +} + +clks_bool clks_userland_init(void) { + clks_log(CLKS_LOG_INFO, "USER", "USERLAND FRAMEWORK ONLINE"); + + if (clks_userland_probe_elf("/shell/shell.elf", "SHELL ELF READY") == CLKS_FALSE) { + return CLKS_FALSE; + } + + if (clks_userland_probe_elf("/system/elfrunner.elf", "ELFRUNNER ELF READY") == CLKS_FALSE) { + return CLKS_FALSE; + } + + if (clks_userland_probe_elf("/system/memc.elf", "MEMC ELF READY") == CLKS_FALSE) { + return CLKS_FALSE; + } + + return CLKS_TRUE; +} + diff --git a/docs/stage7.md b/docs/stage7.md new file mode 100644 index 0000000..fb96bb2 --- /dev/null +++ b/docs/stage7.md @@ -0,0 +1,39 @@ +# CLeonOS Stage7 + +## Stage Goal +- Add user-space foundation under `/cleonos/` with C + Rust mixed development. +- Build first ELF app set: `/shell/shell.elf`, `/system/elfrunner.elf`, `/system/memc.elf`. +- Add user syscall wrapper (`int 0x80`) and minimal user runtime entry (`_start`). +- Integrate user ELF packaging into ramdisk build pipeline. + +## Acceptance Criteria +- Kernel boots and prints `CLEONOS STAGE7 START`. +- FS and userland framework initialize without panic. +- Kernel logs `USERLAND FRAMEWORK ONLINE`. +- Kernel can probe and inspect all required ELF files: + - `/shell/shell.elf` + - `/system/elfrunner.elf` + - `/system/memc.elf` +- `make userapps` outputs 3 ELF files and `make iso` packs them into ramdisk. + +## Build Targets +- `make setup` +- `make userapps` +- `make iso` +- `make run` +- `make debug` + +## QEMU Command +- `qemu-system-x86_64 -M q35 -m 1024M -cdrom build/CLeonOS-x86_64.iso -serial stdio` + +## Common Bugs and Debugging +- `missing tool: rustc` in `make setup`: + - Install Rust toolchain or set `RUSTC` to valid executable path. +- User ELF linking fails: + - Verify `USER_CC`, `USER_LD`, and `cleonos/c/user.ld` are valid. +- `USERLAND INIT FAILED` at boot: + - Confirm ramdisk contains `shell.elf`, `elfrunner.elf`, `memc.elf` in expected directories. +- `ELF INSPECT FAILED` on user files: + - Ensure built apps are ELF64 and not stripped into unsupported format. +- Ramdisk missing user apps: + - Run `make userapps` then `make iso`; check staging path `build/x86_64/ramdisk_root`.