From cd0e1cafae79c4011514676f8cc9510a1c799e48 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Mon, 23 Feb 2026 08:30:43 +0100 Subject: [PATCH] There was an attempt... (AI) --- CMakeLists.txt | 9 ++++ cmake/toolchain-i386-clang.cmake | 59 ++++++++++++++++++++ src/CMakeLists.txt | 33 ++++++++++++ src/boot/boot.s | 92 ++++++++++++++++++++++++++++++++ src/debugport.c | 39 ++++++++++++++ src/include/debugport.h | 34 ++++++++++++ src/include/io.h | 41 ++++++++++++++ src/kernel.c | 45 ++++++++++++++++ src/linker.ld | 53 ++++++++++++++++++ 9 files changed, 405 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/toolchain-i386-clang.cmake create mode 100644 src/CMakeLists.txt create mode 100644 src/boot/boot.s create mode 100644 src/debugport.c create mode 100644 src/include/debugport.h create mode 100644 src/include/io.h create mode 100644 src/kernel.c create mode 100644 src/linker.ld diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e717c9b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) + +project(ClaudeOS C ASM) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Kernel +add_subdirectory(src) diff --git a/cmake/toolchain-i386-clang.cmake b/cmake/toolchain-i386-clang.cmake new file mode 100644 index 0000000..f3dedb6 --- /dev/null +++ b/cmake/toolchain-i386-clang.cmake @@ -0,0 +1,59 @@ +# Cross-compilation toolchain for i386-elf using Clang + LLD. +# +# This targets a bare-metal i386 (80386) environment with no host OS +# libraries. lld is used as the linker because the macOS system ld does +# not support the ELF32 output format required for a GRUB-bootable kernel. + +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR i386) + +# Use Clang for both C and assembly. +set(CMAKE_C_COMPILER clang) +set(CMAKE_ASM_COMPILER clang) + +# Prevent CMake from trying to run the cross-compiled test binaries. +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +# Target triple and architecture flags shared by all compilation steps. +set(CROSS_TARGET "i386-elf") +set(CROSS_ARCH_FLAGS "-target ${CROSS_TARGET} -m32 -march=i386 -mtune=i386") + +# C compiler flags: +# - ffreestanding : no hosted C runtime assumptions +# - fno-stack-protector : no __stack_chk_fail dependency +# - fno-builtin : avoid implicit replacements of standard functions +set(CMAKE_C_FLAGS_INIT + "${CROSS_ARCH_FLAGS} -ffreestanding -fno-stack-protector -fno-builtin -Wall -Wextra" +) + +# Assembler flags: only the flags the assembler front-end actually accepts. +# C-only flags like -ffreestanding, -fno-stack-protector, etc. must NOT be +# passed here — clang treats them as unused and errors out with -Werror. +# -Wno-unused-command-line-argument suppresses CMake's auto-added -MD/-MT/-MF +# dependency-tracking flags that clang ignores in pure assembler mode. +set(CMAKE_ASM_FLAGS_INIT + "-target ${CROSS_TARGET} -m32 -march=i386 -Wno-unused-command-line-argument" +) + +# Linking: +# On aarch64 Alpine the clang driver delegates link steps to the host gcc, +# which does not support -m32 and therefore fails. Bypass the driver entirely +# and call ld.lld directly. The CMake rule variables below replace the normal +# "compile-with-compiler-driver" link invocation. +# +# Rule variables use these placeholders: +# — output binary path +# — all compiled object files +# — extra flags from target_link_options / CMAKE_EXE_LINKER_FLAGS +# — libraries to link (empty for a freestanding kernel) +set(CMAKE_LINKER "ld.lld") +set(CMAKE_C_LINK_EXECUTABLE + "ld.lld -m elf_i386 -o ") +set(CMAKE_ASM_LINK_EXECUTABLE + "ld.lld -m elf_i386 -o ") + +# Keep CMake from accidentally picking up host system headers/libraries. +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..38c51d7 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,33 @@ +set(KERNEL_SOURCES + boot/boot.s + kernel.c + debugport.c +) + +add_executable(kernel ${KERNEL_SOURCES}) + +target_include_directories(kernel PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_compile_options(kernel PRIVATE + # C-only flags — not valid for the assembler front-end. + $<$:-Werror> + $<$:-ffreestanding> + $<$:-fno-stack-protector> + $<$:-fno-exceptions> + # Suppress the harmless "argument unused" warnings that clang emits + # when CMake passes -MD/-MT/-MF to the assembler. + $<$:-Wno-unused-command-line-argument> +) + +set_target_properties(kernel PROPERTIES + OUTPUT_NAME "claudeos.elf" + SUFFIX "" +) + +target_link_options(kernel PRIVATE + # Pass the linker script directly to ld.lld (CMAKE_C_LINK_EXECUTABLE + # calls ld.lld directly, so these are raw ld flags, not clang driver flags). + -T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld +) diff --git a/src/boot/boot.s b/src/boot/boot.s new file mode 100644 index 0000000..78872cb --- /dev/null +++ b/src/boot/boot.s @@ -0,0 +1,92 @@ +/* + * boot.s — Multiboot-compliant bootstrap for ClaudeOS. + * + * This file is the very first code executed by GRUB after the kernel is + * loaded. Its responsibilities are: + * + * 1. Provide a valid Multiboot header so that GRUB recognises the image. + * 2. Zero the BSS segment (the C runtime requires it to start at zero). + * 3. Set up a small temporary stack. + * 4. Forward the Multiboot magic value and info-structure pointer to + * kernel_main() as its first and second arguments. + * 5. Halt the CPU if kernel_main() ever returns (it should not). + * + * Multiboot flags used: + * Bit 0 — 4 KiB-align all boot modules. + * Bit 1 — Include memory map in the Multiboot info structure. + */ + +/* ------------------------------------------------------------------------- + * Multiboot header + * ------------------------------------------------------------------------- */ +.set MULTIBOOT_MAGIC, 0x1BADB002 +.set MULTIBOOT_FLAGS, (1 << 0) | (1 << 1) +.set MULTIBOOT_CHECKSUM, -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS) + +.section .multiboot, "a" +.align 4 +.long MULTIBOOT_MAGIC +.long MULTIBOOT_FLAGS +.long MULTIBOOT_CHECKSUM + +/* ------------------------------------------------------------------------- + * Bootstrap stack — 16 KiB, 16-byte aligned (required by the i386 ABI). + * ------------------------------------------------------------------------- */ +.section .bss +.align 16 +stack_bottom: + .skip 16384 +stack_top: + +/* ------------------------------------------------------------------------- + * Kernel entry point + * ------------------------------------------------------------------------- */ +.section .text +.global _start +.type _start, @function +_start: + /* Set up the temporary kernel stack. */ + mov $stack_top, %esp + + /* Preserve the Multiboot magic before the BSS-clearing loop clobbers EAX. + * + * Registers on entry (Multiboot spec §3.2): + * eax — 0x2BADB002 (magic) + * ebx — multiboot_info_t* (physical address of info structure) + * + * 'rep stosb' only modifies EAX, EDI and ECX, so EBX survives intact. + * We park EAX in ESI (callee-saved, not used by the BSS loop). */ + mov %eax, %esi /* save Multiboot magic */ + + /* Zero the BSS segment. + * The C standard requires that all static storage is zero-initialised. + * The linker script exports _bss_start and _bss_end for this purpose. */ + mov $_bss_start, %edi + mov $_bss_end, %ecx + sub %edi, %ecx /* byte count */ + xor %eax, %eax + rep stosb + + /* Restore magic; EBX (info pointer) is still intact. */ + mov %esi, %eax + + /* + * Call kernel_main(uint32_t multiboot_magic, uint32_t multiboot_info). + * + * The Multiboot specification passes: + * eax — 0x2BADB002 (bootloader magic) + * ebx — physical address of the multiboot_info_t structure + * + * We push them in reverse order to match the C calling convention. + */ + push %ebx /* arg1: multiboot_info pointer */ + push %eax /* arg0: multiboot magic */ + call kernel_main + + /* kernel_main should never return; halt the machine if it does. */ +.L_halt: + cli + hlt + jmp .L_halt + +.size _start, . - _start diff --git a/src/debugport.c b/src/debugport.c new file mode 100644 index 0000000..65055b3 --- /dev/null +++ b/src/debugport.c @@ -0,0 +1,39 @@ +/** + * @file debugport.c + * @brief QEMU/Bochs debug port (I/O port 0xE9) implementation. + * + * Writing a byte to I/O port 0xE9 causes QEMU (when run with + * `-debugcon stdio`) to echo that byte verbatim to the host's stdout. + * This requires no peripheral initialisation and is available from the + * very first instruction after the bootloader hands control to the kernel. + */ + +#include +#include + +/** I/O port address of the QEMU/Bochs debug console. */ +#define DEBUG_PORT 0xE9U + +/** + * @brief Write a single character to the QEMU debug port. + * + * @param c Character to transmit. + */ +void debugport_putc(char c) +{ + outb((uint16_t)DEBUG_PORT, (uint8_t)c); +} + +/** + * @brief Write a NUL-terminated string to the QEMU debug port. + * + * Each character is transmitted individually via debugport_putc(). + * + * @param s Pointer to the NUL-terminated string. Must not be NULL. + */ +void debugport_puts(const char *s) +{ + while (*s) { + debugport_putc(*s++); + } +} diff --git a/src/include/debugport.h b/src/include/debugport.h new file mode 100644 index 0000000..ac67a1e --- /dev/null +++ b/src/include/debugport.h @@ -0,0 +1,34 @@ +/** + * @file debugport.h + * @brief QEMU/Bochs debug port (I/O port 0xE9) output interface. + * + * Port 0xE9 is a de-facto standard "debug port" supported by both Bochs and + * QEMU (when launched with `-debugcon stdio`). Any byte written to this port + * is immediately forwarded to the host's standard output, making it the + * simplest possible kernel logging mechanism — no interrupt handling, VGA, or + * serial initialisation is required. + * + * Usage in QEMU: + * qemu-system-i386 -debugcon stdio ... + */ + +#pragma once + +/** + * @brief Write a single character to the QEMU debug port. + * + * Issues a single OUT instruction to port 0xE9. + * + * @param c Character to transmit. + */ +void debugport_putc(char c); + +/** + * @brief Write a NUL-terminated string to the QEMU debug port. + * + * Iterates over every character in @p s and calls debugport_putc() for each + * one. The terminating NUL byte is not transmitted. + * + * @param s Pointer to the NUL-terminated string. Must not be NULL. + */ +void debugport_puts(const char *s); diff --git a/src/include/io.h b/src/include/io.h new file mode 100644 index 0000000..5b3f30a --- /dev/null +++ b/src/include/io.h @@ -0,0 +1,41 @@ +/** + * @file io.h + * @brief Low-level x86 port I/O helpers. + * + * Provides thin wrappers around the x86 IN/OUT instructions so that C code + * throughout the kernel can read from and write to I/O ports without + * resorting to inline assembly at every call site. + */ + +#pragma once + +#include + +/** + * @brief Write an 8-bit value to an I/O port. + * + * Issues an x86 OUT instruction. Execution halts until the peripheral + * acknowledges the write (the CPU inserts appropriate bus wait-states). + * + * @param port 16-bit I/O port address. + * @param value 8-bit value to send. + */ +static inline void outb(uint16_t port, uint8_t value) +{ + __asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +/** + * @brief Read an 8-bit value from an I/O port. + * + * Issues an x86 IN instruction. + * + * @param port 16-bit I/O port address. + * @return The 8-bit value returned by the peripheral. + */ +static inline uint8_t inb(uint16_t port) +{ + uint8_t value; + __asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} diff --git a/src/kernel.c b/src/kernel.c new file mode 100644 index 0000000..c7925e4 --- /dev/null +++ b/src/kernel.c @@ -0,0 +1,45 @@ +/** + * @file kernel.c + * @brief Kernel entry point. + * + * Called by the Multiboot bootstrap (boot/boot.s) after the BSS has been + * zeroed and a temporary stack has been established. The bootloader passes + * the Multiboot magic value and a pointer to the Multiboot info structure. + */ + +#include +#include + +/** Magic value the bootloader stores in EAX before jumping to _start. */ +#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002U + +/** + * @brief Main kernel entry point. + * + * Validates the Multiboot magic so we know the bootloader handed us a proper + * info structure, then writes a greeting to the QEMU debug port so the host + * can confirm the kernel reached C code successfully. + * + * @param multiboot_magic 0x2BADB002 when booted by a Multiboot-compliant + * bootloader (e.g. GRUB). + * @param multiboot_info Physical address of the multiboot_info_t structure + * provided by the bootloader. + */ +void kernel_main(uint32_t multiboot_magic, uint32_t multiboot_info) +{ + (void)multiboot_info; + + /* Announce ourselves on the QEMU debug port. + * QEMU must be launched with -debugcon stdio for this to appear. */ + if (multiboot_magic == MULTIBOOT_BOOTLOADER_MAGIC) { + debugport_puts("Hello, World!\n"); + debugport_puts("ClaudeOS kernel booted successfully.\n"); + } else { + debugport_puts("ERROR: bad Multiboot magic — not booted by GRUB?\n"); + } + + /* Spin forever. Subsequent commits will add real work here. */ + for (;;) { + __asm__ volatile("hlt"); + } +} diff --git a/src/linker.ld b/src/linker.ld new file mode 100644 index 0000000..0713cd6 --- /dev/null +++ b/src/linker.ld @@ -0,0 +1,53 @@ +/* + * Linker script for the ClaudeOS kernel. + * + * The kernel is loaded by GRUB at physical address 0x00100000 (1 MiB). + * All kernel sections follow immediately after the multiboot header so that + * GRUB can locate it within the first 8 KiB of the ELF image as required by + * the Multiboot specification. + * + * Symbol _bss_start / _bss_end are exported so the bootstrap can zero the + * BSS segment before jumping to C code. _kernel_end marks the first byte + * after the kernel image, which the physical memory allocator will use as + * its starting address. + */ + +OUTPUT_FORMAT(elf32-i386) +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS +{ + . = 0x00100000; + + /* The multiboot header must appear within the first 8 KiB. */ + .multiboot ALIGN(4) : + { + KEEP(*(.multiboot)) + } + + .text ALIGN(4096) : + { + *(.text*) + } + + .rodata ALIGN(4096) : + { + *(.rodata*) + } + + .data ALIGN(4096) : + { + *(.data*) + } + + .bss ALIGN(4096) : + { + _bss_start = .; + *(COMMON) + *(.bss*) + _bss_end = .; + } + + _kernel_end = .; +}