From 63e9a761a36860819d82e152625a6f32c6e5824d Mon Sep 17 00:00:00 2001 From: xamidev Date: Wed, 6 May 2026 11:26:33 +0200 Subject: [PATCH 1/5] should be right? --- include/config.h | 4 +++ include/fs/initfs.h | 2 ++ include/kernel.h | 9 +++-- include/sched/process.h | 14 +++++++- src/arch/x86/syscall.c | 78 ++++++++++++++++++++++++++++++++++++++++- src/fs/initfs.c | 37 +++++++++++++++++++ src/kmain.c | 8 ----- src/sched/process.c | 78 ++++++++++++++--------------------------- 8 files changed, 166 insertions(+), 64 deletions(-) diff --git a/include/config.h b/include/config.h index 4b187da..11c6fe9 100644 --- a/include/config.h +++ b/include/config.h @@ -63,4 +63,8 @@ /* ssp */ #define STACK_CHK_GUARD 0x7ABA5C007ABA5C00 +/* fs */ +#define FDT_MAX 8 // Maximum amount of file descriptors per process + + #endif diff --git a/include/fs/initfs.h b/include/fs/initfs.h index c47d517..266ce33 100644 --- a/include/fs/initfs.h +++ b/include/fs/initfs.h @@ -11,5 +11,7 @@ int initfs_init(struct limine_file* tar_file); int tar_lookup(unsigned char* archive, char* filename, char** out); +int tar_read(char* filename, char** buf); +int tar_exists(const char* filename); #endif \ No newline at end of file diff --git a/include/kernel.h b/include/kernel.h index 01cfb42..cb66dc0 100644 --- a/include/kernel.h +++ b/include/kernel.h @@ -8,10 +8,13 @@ #define KERNEL_H #include "limine.h" + enum ErrorCodes { - ENOMEM, - EIO, - ENOENT + ENOMEM, // No memory + EIO, // Input/output error + ENOENT, // No entry + EBADFD, // Bad file descriptor + EMFILE // Too many open files }; #define CLEAR_INTERRUPTS __asm__ volatile("cli") diff --git a/include/sched/process.h b/include/sched/process.h index af36cb6..bb10c58 100644 --- a/include/sched/process.h +++ b/include/sched/process.h @@ -11,6 +11,7 @@ #include #include #include +#include typedef enum { READY, @@ -18,6 +19,13 @@ typedef enum { DEAD } status_t; +struct fd { + int fd; + char filename[PROCESS_NAME_MAX]; // File opened + uint64_t cursor; // Cursor position in file + bool open; +}; + struct process { size_t pid; char name[PROCESS_NAME_MAX]; @@ -26,6 +34,10 @@ struct process { struct cpu_status* context; void* root_page_table; // Process PML4 (should contain kernel PML4 in higher half [256-511] void* kernel_stack; // Used for interrupts (syscall: int 0x80), defines the TSS RSP0 + + struct fd fdt[FDT_MAX]; // File Descriptor Table + size_t next_free_fd; + struct process* next; }; @@ -38,7 +50,7 @@ void process_exit(void); void process_display_list(struct process* processes_list); -void process_create_user(struct limine_file* file, char* name); + void process_create_user_raw(char* file, int size, char* name); #endif diff --git a/src/arch/x86/syscall.c b/src/arch/x86/syscall.c index 839969f..b402877 100644 --- a/src/arch/x86/syscall.c +++ b/src/arch/x86/syscall.c @@ -4,15 +4,84 @@ * @license GPL-3.0-only */ +#include "config.h" #include "sched/scheduler.h" #include #include #include #include #include +#include +#include +#include extern struct process* current_process; +// Return fd on success, -errno on error +int sys_open(const char* filename, int flags) +{ + if (tar_exists(filename) < 0) { + return -ENOENT; // file doesn't exist.. + } + // file exists here! + if (current_process->next_free_fd >= FDT_MAX) { + return -EMFILE; + } + int fd = current_process->next_free_fd++; + current_process->fdt[fd].fd = fd; + current_process->fdt[fd].open = true; + current_process->fdt[fd].cursor = 0; + strncpy(current_process->fdt[fd].filename, filename, PROCESS_NAME_MAX - 1); + return fd; +} + +// Return 0 on success, -EBADFD if invalid FD +int sys_close(int fd) +{ + if (fd < 0 || fd >= FDT_MAX) { + return -EBADFD; + } + + if (!current_process->fdt[fd].open) { + return -EBADFD; // FD not opened in the first place + } + + current_process->fdt[fd].open = false; + current_process->fdt[fd].filename[0] = '\0'; + current_process->fdt[fd].cursor = 0; + return 0; +} + +// Should return the number of bytes read +int sys_read(unsigned int fd, char* buf, size_t count) +{ + size_t i; + switch (fd) { + case 0: //read from stdin (keyboard) + for (i=0; ifdt[fd].open == false) { + return -EBADFD; // File descriptor wasn't open + } + // Here fd refers to a valid opened file.. + int sz = tar_read(current_process->fdt[fd].filename,&buf); + if (sz == 0) { + return -ENOENT; + } else { + return sz; + } + } + + return -EBADFD; +} + +// TODO: Should have a return value: number of bytes written on success, -1 on error (errno set) void sys_write(unsigned int fd, const char* buf, size_t count) { switch (fd) { @@ -61,14 +130,21 @@ struct cpu_status* syscall_handler(struct cpu_status* regs) switch (regs->rax) { case 0: //sys_read + regs->rax = sys_read(regs->rdi, (char*)regs->rsi, regs->rdx); break; case 1: //sys_write sys_write(regs->rdi, (char*)regs->rsi, regs->rdx); break; + case 2: + regs->rax = sys_open((const char*)regs->rdi, regs->rsi); + break; + case 3: + regs->rax = sys_close(regs->rdi); + break; case 60: //sys_exit sys_exit(regs->rdi); break; - default: + default: // bad syscall regs->rax = 0xbad515ca11; break; } diff --git a/src/fs/initfs.c b/src/fs/initfs.c index ddeff2b..2e49243 100644 --- a/src/fs/initfs.c +++ b/src/fs/initfs.c @@ -59,6 +59,43 @@ int tar_lookup(unsigned char* archive, char* filename, char** out) return 0; } +/* + * tar_read - read a file in the TAR file + * @filename: file to read (absolute path) + * @out: out buffer (if file is found) + * + * Return: + * $filesize - size of the file, if found + * $0 - file not found + */ +int tar_read(char* filename, char** buf) +{ + return tar_lookup(archive_start_addr, filename, buf); +} + +/* + * tar_exists - check if a file exists in the TAR archive + * @filename: file to check (absolute path) + * + * Return: + * $filesize - size of the file, if found + * $-ENOENT - file not found + */ +int tar_exists(const char* filename) +{ + unsigned char* ptr = archive_start_addr; + + while (!memcmp(ptr + 257, "ustar", 5)) { + int filesize = tar_oct2bin(ptr + 0x7c, 11); + if (!memcmp(ptr, filename, strlen(filename) + 1)) { + return filesize; + } + ptr += (((filesize + 511) / 512) + 1) * 512; + } + return -ENOENT; +} + + /* * initfs_init - initialize the TAR initial filesystem * @tar_file: pointer to the Limine-loaded archive diff --git a/src/kmain.c b/src/kmain.c index c0ec88b..33ee671 100644 --- a/src/kmain.c +++ b/src/kmain.c @@ -78,14 +78,6 @@ void idle_main(void* arg) } } -void thing_main(void* arg) -{ - printf("What's your name, pal? "); - char name[10]; - keyboard_getline(name, 10); - printf("\r\n{%s} is such a nice name!\r\n", name); -} - extern uintptr_t kheap_start; /* diff --git a/src/sched/process.c b/src/sched/process.c index e7d72d6..633555e 100644 --- a/src/sched/process.c +++ b/src/sched/process.c @@ -225,59 +225,15 @@ void process_jump_to_user(uintptr_t stack_top, uintptr_t user_code) extern struct tss tss; /* - * process_create_user - Create a new user process - * @file: pointer to Limine file structure + * process_create_user_raw - Create a new user process from raw binary + * @file: pointer to beginning of binary + * @size: size of the binary * @name: name for the new process * - * This function takes a loaded Limine executable - * module, and maps its code, a user stack, sets the - * TSS RSP0 for interrupts, and finally jumps to the - * user code. + * This function takes an executable loaded in memory + * and maps its code, a user stack, sets the TSS RSP0 + * for interrupts, and finally jumps to the user code. */ -void process_create_user(struct limine_file* file, char* name) -{ - CLEAR_INTERRUPTS; - struct process* proc = (struct process*)kmalloc(sizeof(struct process)); - struct cpu_status* ctx = (struct cpu_status*)kmalloc(sizeof(struct cpu_status)); - - if (!proc || !ctx) panic(NULL, "out of memory while creating user process"); - - strncpy(proc->name, name, PROCESS_NAME_MAX); - memset(ctx, 0, sizeof(struct cpu_status)); // set GP registers to zero - proc->pid = next_free_pid++; - proc->status = READY; - proc->next = 0; - proc->context = ctx; - proc->context->iret_ss = USER_DATA_SEGMENT | 3; - proc->context->iret_cs = USER_CODE_SEGMENT | 3; - proc->context->iret_flags = 0x202; // Interrupt Flag set - - void* exec_addr = file->address; - uint64_t exec_size = file->size; - - uint64_t* user_pml4 = vmm_create_address_space(); - if (!user_pml4) panic(NULL, "failed to create user address space"); - proc->root_page_table = user_pml4; - - uintptr_t stack_top = vmm_alloc_user_stack(user_pml4); - uint64_t code = vmm_alloc_user_code(user_pml4, exec_addr, exec_size); - - proc->context->iret_rsp = stack_top; - proc->context->iret_rip = code; - proc->kernel_stack = kalloc_stack(); - if (!proc->kernel_stack) panic(NULL, "failed to allocate kernel stack"); - - // Copy code into user pages; for that we need to temporarily switch to the user pml4 - load_cr3(VIRT_TO_PHYS((uint64_t)user_pml4)); - memcpy((uint64_t*)code, exec_addr, exec_size); - load_cr3(VIRT_TO_PHYS((uint64_t)kernel_pml4)); - - process_add(&processes_list, proc); - DEBUG("user process '%s' (pid=%u) enqueued for scheduling", name, proc->pid); - SET_INTERRUPTS; -} - -// Same as above but for a raw data pointer (pointing to raw binary, no ELF) void process_create_user_raw(char* file, int size, char* name) { CLEAR_INTERRUPTS; @@ -286,8 +242,10 @@ void process_create_user_raw(char* file, int size, char* name) if (!proc || !ctx) panic(NULL, "out of memory while creating user process"); + memset(proc, 0, sizeof(struct process)); + memset(ctx, 0, sizeof(struct cpu_status)); + strncpy(proc->name, name, PROCESS_NAME_MAX); - memset(ctx, 0, sizeof(struct cpu_status)); // set GP registers to zero proc->pid = next_free_pid++; proc->status = READY; proc->next = 0; @@ -296,6 +254,24 @@ void process_create_user_raw(char* file, int size, char* name) proc->context->iret_cs = USER_CODE_SEGMENT | 3; proc->context->iret_flags = 0x202; // Interrupt Flag set + /* Set basic entries for the process's File Descriptor Table */ + proc->fdt[0].fd = 0; + proc->fdt[0].open = true; + proc->fdt[0].cursor = 0; + strncpy(proc->fdt[0].filename, "stdin", PROCESS_NAME_MAX - 1); + + proc->fdt[1].fd = 1; + proc->fdt[1].open = true; + proc->fdt[1].cursor = 0; + strncpy(proc->fdt[1].filename, "stdout", PROCESS_NAME_MAX - 1); + + proc->fdt[2].fd = 2; + proc->fdt[2].open = true; + proc->fdt[2].cursor = 0; + strncpy(proc->fdt[2].filename, "stderr", PROCESS_NAME_MAX - 1); + + proc->next_free_fd = 3; // file descriptors are also bump-allocated + void* exec_addr = (void*)file; uint64_t exec_size = size; From c061da4d813c8a748a174fa54886c89b2c299115 Mon Sep 17 00:00:00 2001 From: xamidev Date: Wed, 6 May 2026 13:29:35 +0200 Subject: [PATCH 2/5] sys_read/open/close --- Makefile | 10 +++++----- docs/MANUAL.md | 8 ++++++-- include/fs/initfs.h | 2 +- include/kernel.h | 3 ++- src/arch/x86/syscall.c | 43 ++++++++++++++++++++++++------------------ src/fs/initfs.c | 32 ++++++++++++++++++++++++------- user/pedicel.S | 32 ++++++++++++++++++++++++++++--- user/wow.txt | 1 + 8 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 user/wow.txt diff --git a/Makefile b/Makefile index a5fb9a4..edc5a21 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,6 @@ CC_FLAGS=-Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fstack-protector -fn LD := x86_64-elf-ld $(ELFFILE): $(BUILDDIR) $(OBJFILES) - nasm -f bin user/hello.S -o $(BUILDDIR)/hello - nasm -f bin user/pedicel.S -o $(BUILDDIR)/pedicel - tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) hello pedicel - nasm -f elf64 src/arch/x86/idt.S -o $(BUILDDIR)/idt_stub.o $(LD) -o $(ELFFILE) -T linker.ld $(OBJFILES) $(BUILDDIR)/idt_stub.o # Get the symbols for debugging @@ -45,7 +41,11 @@ limine/limine: git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1 $(MAKE) -C limine -initfs: +.PHONY: user +user: + nasm -f bin user/hello.S -o $(BUILDDIR)/hello + nasm -f bin user/pedicel.S -o $(BUILDDIR)/pedicel + tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) hello pedicel -C ../user wow.txt build-iso: limine/limine $(ELFFILE) rm -rf iso_root diff --git a/docs/MANUAL.md b/docs/MANUAL.md index c55e67a..b8dcda2 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -41,8 +41,12 @@ The recommended hardware to run PepperOS is the following: ## III. Syscall table The syscall interface in the Pepper kernel uses the System V ABI convention for argument order. +It vaguely mimics Unix-like systems. Name | Number (%rax) | arg0 (%rdi) | arg1 (%rsi) | arg2 (%rdx) | |---|---|---|---|---| -| sys_write | 1 | unsigned int fd | const char* buf | size_t count | | -| sys_exit | 60 | int error_code | | | | \ No newline at end of file +| sys_read | 0 | unsigned int fd | char* buf | size_t count | +| sys_write | 1 | unsigned int fd | const char* buf | size_t count | +| sys_open | 2 | const char* filename | int flags | | +| sys_close | 3 | unsigned int fd | | | +| sys_exit | 60 | int error_code | | | \ No newline at end of file diff --git a/include/fs/initfs.h b/include/fs/initfs.h index 266ce33..a8c12c9 100644 --- a/include/fs/initfs.h +++ b/include/fs/initfs.h @@ -11,7 +11,7 @@ int initfs_init(struct limine_file* tar_file); int tar_lookup(unsigned char* archive, char* filename, char** out); -int tar_read(char* filename, char** buf); int tar_exists(const char* filename); +int tar_read(char* filename, char* out, int count, int offset); #endif \ No newline at end of file diff --git a/include/kernel.h b/include/kernel.h index cb66dc0..3d8d0b7 100644 --- a/include/kernel.h +++ b/include/kernel.h @@ -14,7 +14,8 @@ enum ErrorCodes { EIO, // Input/output error ENOENT, // No entry EBADFD, // Bad file descriptor - EMFILE // Too many open files + EMFILE, // Too many open files + EINVAL // Invalid argument }; #define CLEAR_INTERRUPTS __asm__ volatile("cli") diff --git a/src/arch/x86/syscall.c b/src/arch/x86/syscall.c index b402877..15f9128 100644 --- a/src/arch/x86/syscall.c +++ b/src/arch/x86/syscall.c @@ -70,10 +70,12 @@ int sys_read(unsigned int fd, char* buf, size_t count) return -EBADFD; // File descriptor wasn't open } // Here fd refers to a valid opened file.. - int sz = tar_read(current_process->fdt[fd].filename,&buf); + int sz = tar_read(current_process->fdt[fd].filename, buf, count, + current_process->fdt[fd].cursor); if (sz == 0) { return -ENOENT; } else { + current_process->fdt[fd].cursor += sz; return sz; } } @@ -82,27 +84,25 @@ int sys_read(unsigned int fd, char* buf, size_t count) } // TODO: Should have a return value: number of bytes written on success, -1 on error (errno set) -void sys_write(unsigned int fd, const char* buf, size_t count) +int sys_write(unsigned int fd, const char* buf, size_t count) { switch (fd) { case 1: //stdout - for (size_t i=0; istatus = DEAD; - DEBUG("exiting process PID=%u name=%s", current_process->pid, current_process->name); + DEBUG("(pid=%u, name=%s)", current_process->pid, current_process->name); + return error_code; } /* @@ -125,29 +125,36 @@ void sys_exit(int error_code) */ struct cpu_status* syscall_handler(struct cpu_status* regs) { - DEBUG("Syscall %lx with (arg0=%lx arg1=%lx)", regs->rax, regs->rdi, regs->rsi); - switch (regs->rax) { - case 0: //sys_read + case 0: + DEBUG("sys_read(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx); regs->rax = sys_read(regs->rdi, (char*)regs->rsi, regs->rdx); break; - case 1: //sys_write - sys_write(regs->rdi, (char*)regs->rsi, regs->rdx); + case 1: + DEBUG("sys_write(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx); + regs->rax = sys_write(regs->rdi, (char*)regs->rsi, regs->rdx); break; case 2: + DEBUG("sys_open(filename=%s, flags=%u)", regs->rdi, regs->rsi); regs->rax = sys_open((const char*)regs->rdi, regs->rsi); break; case 3: + DEBUG("sys_close(fd=%u)", regs->rdi); regs->rax = sys_close(regs->rdi); break; - case 60: //sys_exit - sys_exit(regs->rdi); + case 60: + DEBUG("sys_exit(error_code=%d)", regs->rdi); + regs->rax = sys_exit(regs->rdi); break; - default: // bad syscall + default: + DEBUG("Bad syscall! (rax=%p, rdi=%p, rsi=%p, rdx=%p)", + regs->rax, regs->rdi, regs->rsi, regs->rdx); regs->rax = 0xbad515ca11; break; } + DEBUG("returned rax=%p (%u)", regs->rax, regs->rax); + return regs; } \ No newline at end of file diff --git a/src/fs/initfs.c b/src/fs/initfs.c index 2e49243..40b314c 100644 --- a/src/fs/initfs.c +++ b/src/fs/initfs.c @@ -42,7 +42,7 @@ int tar_oct2bin(unsigned char* str, int size) * * Return: * $filesize - size of the file, if found - * $0 - file not found + * $-ENOENT - file not found */ int tar_lookup(unsigned char* archive, char* filename, char** out) { @@ -56,21 +56,39 @@ int tar_lookup(unsigned char* archive, char* filename, char** out) } ptr += (((filesize + 511) / 512) + 1) * 512; } - return 0; + return -ENOENT; } /* - * tar_read - read a file in the TAR file + * tar_read - read a file in the TAR archive * @filename: file to read (absolute path) - * @out: out buffer (if file is found) + * @out: where to store file data if found + * @count: amount of bytes to read + * @offset: read from byte offset (0 for none) * * Return: * $filesize - size of the file, if found - * $0 - file not found + * $-ENOENT - file not found */ -int tar_read(char* filename, char** buf) +int tar_read(char* filename, char* out, int count, int offset) { - return tar_lookup(archive_start_addr, filename, buf); + char* file_data; + int filesize = tar_lookup(archive_start_addr, filename, &file_data); + + if (filesize <= 0) { + return filesize; + } + + if (offset >= filesize) { + return -EINVAL; + } + + int remaining = filesize - offset; + int to_read = remaining < count ? remaining : count; + + memcpy(out, file_data + offset, to_read); + + return to_read; } /* diff --git a/user/pedicel.S b/user/pedicel.S index e1b92ef..28bab89 100644 --- a/user/pedicel.S +++ b/user/pedicel.S @@ -1,7 +1,8 @@ bits 64 section .data -hello db 0x0A, 0x0D, "User program 2 speaking", 0x0A, 0x0D, 0 +hello db 0x0A, 0x0D, "TEST PROGRAM...", 0x0A, 0x0D, 0 +filename db "wow.txt", 0 section .text @@ -9,7 +10,29 @@ _start: mov rax, 0x1 ;sys_write mov rdi, 0x1 ;stdout lea rsi, [rel hello] - mov rdx, 27 ;count + mov rdx, 19 ;count + int 0x80 + + ; Open a file + mov rax, 0x2 ;sys_open + lea rdi, [rel filename] ;filename + mov rsi, 0x0 ;flags + int 0x80 + + mov rdi, rax ;fd + mov rax, 0x0 ;sys_read + lea rsi, [rel buf] ;buf + mov rdx, 33 ;count + int 0x80 + + mov rax, 0x1 ;sys_write + mov rdi, 0x1 ;stdout + lea rsi, [rel buf] ;buf + mov rdx, 33 ;count + int 0x80 + + mov rax, 0x3 ;sys_close + mov rdi, 0x3 ;fd int 0x80 ; when we are ready to have an os specific toolchain, @@ -22,4 +45,7 @@ _start: int 0x80 .loop: - jmp .loop \ No newline at end of file + jmp .loop + +section .bss +buf resb 10 \ No newline at end of file diff --git a/user/wow.txt b/user/wow.txt new file mode 100644 index 0000000..ed7191d --- /dev/null +++ b/user/wow.txt @@ -0,0 +1 @@ +hi from a file opened in usermode \ No newline at end of file From 9a1a0e428a19289c4e906f3a4c9b09909db6eb27 Mon Sep 17 00:00:00 2001 From: xamidev Date: Wed, 6 May 2026 14:04:28 +0200 Subject: [PATCH 3/5] tar_list --- include/fs/initfs.h | 1 + src/fs/initfs.c | 16 ++++++++++++++++ src/kapps/kshell.c | 27 ++++++++++++++++----------- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/include/fs/initfs.h b/include/fs/initfs.h index a8c12c9..f7a3ca8 100644 --- a/include/fs/initfs.h +++ b/include/fs/initfs.h @@ -13,5 +13,6 @@ int initfs_init(struct limine_file* tar_file); int tar_lookup(unsigned char* archive, char* filename, char** out); int tar_exists(const char* filename); int tar_read(char* filename, char* out, int count, int offset); +void tar_list(); #endif \ No newline at end of file diff --git a/src/fs/initfs.c b/src/fs/initfs.c index 40b314c..50c34dd 100644 --- a/src/fs/initfs.c +++ b/src/fs/initfs.c @@ -59,6 +59,22 @@ int tar_lookup(unsigned char* archive, char* filename, char** out) return -ENOENT; } +/* + * tar_list - list all files present in archive + */ +void tar_list() +{ + printf("++ Contents of initial filesystem ++\r\n\r\n"); + unsigned char *ptr = archive_start_addr; + + while (!memcmp(ptr + 257, "ustar", 5)) { + int filesize = tar_oct2bin(ptr + 0x7c, 11); + char* filename = (char*)ptr; + printf("file: %s\r\n", filename); + ptr += (((filesize + 511) / 512) + 1) * 512; + } +} + /* * tar_read - read a file in the TAR archive * @filename: file to read (absolute path) diff --git a/src/kapps/kshell.c b/src/kapps/kshell.c index 46cd6dc..fa44b51 100644 --- a/src/kapps/kshell.c +++ b/src/kapps/kshell.c @@ -4,6 +4,7 @@ * @license GPL-3.0-only */ +#include "fs/initfs.h" #include #include #include @@ -42,17 +43,16 @@ void pedicel_main(void* arg) keyboard_getline(input_buf, PEDICEL_INPUT_SIZE); if (strncmp(input_buf, "help", 4) == 0) { - printf("\r\nYou are currently running the test kernel shell. This is not\r\n" - "a fully-fledged shell like you'd find in a complete operating system,\r\n" - "but rather a toy to play around in the meantime.\r\n\r\n" - "clear - clear the screen\r\n" - "panic - trigger a test panic\r\n" - "syscall - trigger int 0x80\r\n" - "pf - trigger a page fault\r\n" - "now - get current date\r\n" - "smash - smash the stack\r\n" - "mem - get used heap info\r\n" - "load - load an user executable\r\n"); + printf("++ shell builtins ++\r\n\r\n" + "\tclear - clear the screen\r\n" + "\tpanic - trigger a test panic\r\n" + "\tsyscall - trigger int 0x80\r\n" + "\tpf - trigger a page fault\r\n" + "\tnow - get current date\r\n" + "\tsmash - smash the stack\r\n" + "\tmem - get used heap info\r\n" + "\tload - load an user executable\r\n" + "\tlist - list initfs.tar contents\r\n"); continue; } @@ -102,6 +102,11 @@ void pedicel_main(void* arg) continue; } + if (strncmp(input_buf, "list", 4) == 0) { + tar_list(); + continue; + } + printf("%s: command not found\r\n", input_buf); } } \ No newline at end of file From eb8a03facdc6a1d850a6ed1c75258be96b30de4f Mon Sep 17 00:00:00 2001 From: xamidev Date: Fri, 8 May 2026 12:38:16 +0200 Subject: [PATCH 4/5] Load raw C binary + docs --- Makefile | 7 +++--- README.md | 31 ++++-------------------- docs/SOFTWARE.md | 57 +++++++++++++++++++++++++++++++++++++++++++++ docs/STYLE.md | 8 +++++++ user/Makefile | 18 ++++++++++++++ user/apex.c | 7 ++++++ user/hello.S | 21 ----------------- user/libc/crt0.S | 18 ++++++++++++++ user/libc/linker.ld | 22 +++++++++++++++++ user/libc/syscall.h | 30 ++++++++++++++++++++++++ 10 files changed, 169 insertions(+), 50 deletions(-) create mode 100644 docs/SOFTWARE.md create mode 100644 user/Makefile create mode 100644 user/apex.c delete mode 100644 user/hello.S create mode 100644 user/libc/crt0.S create mode 100644 user/libc/linker.ld create mode 100644 user/libc/syscall.h diff --git a/Makefile b/Makefile index edc5a21..631447b 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +USER_PROGRAMS := pedicel.raw apex.raw +USER_FILES := wow.txt BUILDDIR := build ELFFILE := pepperk @@ -43,9 +45,8 @@ limine/limine: .PHONY: user user: - nasm -f bin user/hello.S -o $(BUILDDIR)/hello - nasm -f bin user/pedicel.S -o $(BUILDDIR)/pedicel - tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) hello pedicel -C ../user wow.txt + $(MAKE) -C user + tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) $(USER_PROGRAMS) -C ../user $(USER_FILES) build-iso: limine/limine $(ELFFILE) rm -rf iso_root diff --git a/README.md b/README.md index 33254bb..1956c3e 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,9 @@ CC := gcc LD := ld ``` -Then, to compile the kernel and make an ISO image file, run: `make build-iso` -To run it with QEMU, do: `make run` +Then, to compile the kernel and make an ISO image file, run: `make`. +To build the user programs and the initial filesystem, do `make user`. +To run it with QEMU, do: `make run`. ## Trying the kernel on real hardware @@ -48,31 +49,9 @@ These features can be activated by setting them to "true" at the end of the make make UBSAN=true ``` -## TODO +## Writing software for PepperOS -The basics that I'm targeting are: - -### Basic utility of what we call a "kernel" - -- Implement tasks, and task switching + context switching and spinlock acquire/release -- Load an executable -- Filesystem (TAR for read-only initfs, then maybe read-write using FAT12/16/32 or easier fs) w/ VFS layer -- Getting to userspace (ring 3 switching, syscall interface) -- Porting musl libc or equivalent - -### Scalability/maintenance/expansion features - -- Documentation -- SOME error handling in functions -- Unit tests -- Good error codes (like Linux kernel: ENOMEM, ENOENT, ...) - -### Optional features - -In the future, maybe? -- SMP support (Limine provides functionality to make this easier) -- Parsing the ACPI tables and using them for something -- Replacing the PIT timer with APIC +If you want to write software for PepperOS, take a look at the [Software Developer's guide](docs/SOFTWARE.md). ## Thanks diff --git a/docs/SOFTWARE.md b/docs/SOFTWARE.md new file mode 100644 index 0000000..c7e57a0 --- /dev/null +++ b/docs/SOFTWARE.md @@ -0,0 +1,57 @@ +# Writing software for PepperOS + +## Why would you want to do that? + +Honestly I have no idea. Maybe you have too much free time. +Keep in mind that the Pepper kernel is a personal project and it's full of bugs, inconsistencies, weird ways of doing things (and I don't care because it's my toy). +Now if you still want to write something for this OS, thank you. Follow along. + +## 1. Write the source code + +PepperOS is able to run programs written in x86 assembly, and C programs. + +### x86 Assembly + +Start your assembly file with the `bits 64` instruction, to emit 64-bit code. +You can add sections `.text`, `.data`, `.bss` as you need. +The three things to take in consideration here are: +- PepperOS does not use the `syscall` instruction, instead it uses the old-fashioned `int 0x80` to trigger a system call. +- The entry point should be labelled as `_start`. +- At the end of the file, there should be an exit system call followed by a loop, like so: + +```nasm +.end: + mov rax, 0x3C + mov rdi, 0x0 + int 0x80 + +.loop: + jmp .loop +``` + +For an example, look at the file [pedicel.S](../user/pedicel.S). + +### C program + +You will find relevant headers in the `libc` directory. They contain system call wrappers, utility functions, and more. See what's implemented there and what's not. +To invoke a system call you can use the functions defined in `libc/syscall.h`. + +## 2. Add the Makefile rule and variable + +Now that your code is complete, add a Makefile rule to `user/Makefile` with your program name. You can just copy-paste the rule that applies to you (either from an Assembly source or C source) and change the name of the files (.raw, .elf, etc...) in the rule. +For clarity, raw binaries have the `.raw` extension, and ELF ones have `.elf`. +You also now have to add the name of the executable to the `USER_PROGRAMS` variable at the top of the global Makefile. +Finally, do `make user` to compile your program. + +## 3. Run your program + +You can now boot up PepperOS, in a VM or on real hardware, and use the kernel's shell to `list` files in the filesystem (to see if your executable was properly added), and then, run it with the `load` command. Congratulations, you made a program for a random hobby OS! + +## 4. (Optional) debugging + +Use GDB with the `make debug` rule! +For your information, user programs are loaded at `0x400000`. Can be good to know to set breakpoints. + +## 5. (Optional) contribute! + +If you like what you've done and you think it could be nice to add it to PepperOS, send it to me by e-mail: `xamidev (at) riseup (dot) net`. It may or may not be added in a future release... who knows? \ No newline at end of file diff --git a/docs/STYLE.md b/docs/STYLE.md index 6321955..f43e30b 100644 --- a/docs/STYLE.md +++ b/docs/STYLE.md @@ -2,6 +2,14 @@ This document describes the coding style for the Pepper kernel. It is used as a guideline across all source files. +## Setting up a language server (optional) + +Before you do anything you might want to setup a language server with your editor. This will save you lots of time correcting errors and stuff. I use `clangd`, and generate my `compile_commands.json` like so: + +``` +bear -- make +``` + ## Indentation Indentations should be 4 characters long. diff --git a/user/Makefile b/user/Makefile new file mode 100644 index 0000000..5844db3 --- /dev/null +++ b/user/Makefile @@ -0,0 +1,18 @@ +CC := x86_64-elf-gcc +CC_FLAGS := -ffreestanding -nostdlib -fno-pic -mno-red-zone -Ilibc + +LD := x86_64-elf-ld + +BUILDDIR := ../build +LIBDIR := libc + +all: pedicel apex + +pedicel: + nasm -f bin pedicel.S -o $(BUILDDIR)/pedicel.raw + +apex: + $(CC) $(CC_FLAGS) -c apex.c -o $(BUILDDIR)/apex.o + nasm -f elf64 $(LIBDIR)/crt0.S -o $(BUILDDIR)/crt0.o + $(LD) -T $(LIBDIR)/linker.ld $(BUILDDIR)/crt0.o $(BUILDDIR)/apex.o -o $(BUILDDIR)/apex.elf + objcopy -O binary $(BUILDDIR)/apex.elf $(BUILDDIR)/apex.raw \ No newline at end of file diff --git a/user/apex.c b/user/apex.c new file mode 100644 index 0000000..158b571 --- /dev/null +++ b/user/apex.c @@ -0,0 +1,7 @@ +#include + +int main() { + const char* msg = "hi from C userland\r\n"; + write(1, msg, 21); + return 42; +} \ No newline at end of file diff --git a/user/hello.S b/user/hello.S deleted file mode 100644 index 3f063c5..0000000 --- a/user/hello.S +++ /dev/null @@ -1,21 +0,0 @@ -bits 64 - -section .data - hi db "hi from userland :) we did it man", 0x0A, 0x0d, 0 - -section .text - -hello: - mov rax, 0x1 ;sys_write - mov rdi, 0x1 ;stdout - lea rsi, [rel hi] ;char* buf - mov rdx, 35 ;count - int 0x80 - -.end: - mov rax, 0x3C ;sys_exit - mov rdi, 0x0 ;error_code - int 0x80 - -.loop: - jmp .loop \ No newline at end of file diff --git a/user/libc/crt0.S b/user/libc/crt0.S new file mode 100644 index 0000000..1679f5a --- /dev/null +++ b/user/libc/crt0.S @@ -0,0 +1,18 @@ +bits 64 +global _start +extern main + +section .text + +; Begin the program with main() function +_start: + call main + +; Exit the program by exit() syscall +.exit: + mov rdi, rax ; put the value of "return X;" (rax) as arg1 (error_code) + mov rax, 60 ; sys_exit + int 0x80 + +.loop: + jmp .loop \ No newline at end of file diff --git a/user/libc/linker.ld b/user/libc/linker.ld new file mode 100644 index 0000000..0aa08d6 --- /dev/null +++ b/user/libc/linker.ld @@ -0,0 +1,22 @@ +ENTRY(_start) + +SECTIONS +{ + . = 0x400000; + + .text : { + *(.text*) + } + + .rodata : { + *(.rodata*) + } + + .data : { + *(.data*) + } + + .bss : { + *(.bss*) + } +} \ No newline at end of file diff --git a/user/libc/syscall.h b/user/libc/syscall.h new file mode 100644 index 0000000..21861ae --- /dev/null +++ b/user/libc/syscall.h @@ -0,0 +1,30 @@ +#pragma once + +// 3 because 3 arguments to the call, get it?? +static inline long syscall3(long n, long a, long b, long c) { + long ret; + + __asm__ volatile ( + "int $0x80" + : "=a"(ret) + : "a"(n), "D"(a), "S"(b), "d"(c) + : "memory" + ); + + return ret; +} + +static inline void write(int fd, const char* buf, long len) { + syscall3(1, fd, (long)buf, len); +} + +static inline void exit(int code) { + __asm__ volatile ( + "int $0x80" + : + : "a"(60), "D"(code) + : "memory" + ); + + for (;;); +} \ No newline at end of file From 8c5911bef922067f283dca0bd64c4b280849b4d2 Mon Sep 17 00:00:00 2001 From: xamidev Date: Fri, 8 May 2026 12:58:08 +0200 Subject: [PATCH 5/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1956c3e..7d75366 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # red-pepper pepperOS: "will never be done" - +pepper ## Description PepperOS is a 64-bit freely-licensed monolithic kernel for x86 processors, with round-robin preemptive scheduling and 4-level paging. See the [manual](docs/MANUAL.md) for more.