There was an attempt... (AI)

This commit is contained in:
2026-02-23 08:30:43 +01:00
parent f42d65267b
commit cd0e1cafae
9 changed files with 405 additions and 0 deletions

9
CMakeLists.txt Normal file
View File

@@ -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)

View File

@@ -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:
# <TARGET> — output binary path
# <OBJECTS> — all compiled object files
# <LINK_FLAGS> — extra flags from target_link_options / CMAKE_EXE_LINKER_FLAGS
# <LINK_LIBRARIES> — libraries to link (empty for a freestanding kernel)
set(CMAKE_LINKER "ld.lld")
set(CMAKE_C_LINK_EXECUTABLE
"ld.lld -m elf_i386 <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
set(CMAKE_ASM_LINK_EXECUTABLE
"ld.lld -m elf_i386 <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
# 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)

33
src/CMakeLists.txt Normal file
View File

@@ -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.
$<$<COMPILE_LANGUAGE:C>:-Werror>
$<$<COMPILE_LANGUAGE:C>:-ffreestanding>
$<$<COMPILE_LANGUAGE:C>:-fno-stack-protector>
$<$<COMPILE_LANGUAGE:C>:-fno-exceptions>
# Suppress the harmless "argument unused" warnings that clang emits
# when CMake passes -MD/-MT/-MF to the assembler.
$<$<COMPILE_LANGUAGE:ASM>:-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
)

92
src/boot/boot.s Normal file
View File

@@ -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

39
src/debugport.c Normal file
View File

@@ -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 <debugport.h>
#include <io.h>
/** 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++);
}
}

34
src/include/debugport.h Normal file
View File

@@ -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);

41
src/include/io.h Normal file
View File

@@ -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 <stdint.h>
/**
* @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;
}

45
src/kernel.c Normal file
View File

@@ -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 <stdint.h>
#include <debugport.h>
/** 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");
}
}

53
src/linker.ld Normal file
View File

@@ -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 = .;
}