From eb8a03facdc6a1d850a6ed1c75258be96b30de4f Mon Sep 17 00:00:00 2001 From: xamidev Date: Fri, 8 May 2026 12:38:16 +0200 Subject: [PATCH] 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