8 Commits

Author SHA1 Message Date
cd0e1cafae There was an attempt... (AI) 2026-02-23 08:30:43 +01:00
f42d65267b Force devcontainer to use amd64 (human) 2026-02-23 08:30:31 +01:00
6d9ab26423 Update readme (human) 2026-02-23 08:15:11 +01:00
e8fa7e1f28 Update devcontainer (AI+human) 2026-02-23 08:13:18 +01:00
1cf39a70cf Update instructions (human) 2026-02-23 08:12:51 +01:00
bbd7d3725b Add devcontainer for i386 kernel development (AI)
Provides a reproducible Debian-based build environment with:
- Clang + LLD for cross-compilation to i386-elf targets
- GRUB 2 tools (grub-pc-bin, xorriso, mtools) for bootable
  ISO and floppy image generation
- QEMU (i386) for integration test execution
- CMake + Ninja for the build system

The Dockerfile includes a self-test that verifies Clang + lld can
produce a valid ELF 32-bit i386 binary before the image is accepted.
2026-02-23 07:13:49 +01:00
f3ef92be4f Create directory structure (AI)
Set up the project directory structure as specified in the README:
- apps/: for application source code (e.g. ls, sh)
- docs/: for subsystem documentation
- libs/: for shared libraries used by apps and/or the kernel
- src/: for kernel source code
- src/it/: for integration tests
- vendor/: for 3rd-party source code (AI will not modify this)

The build/ directory is excluded via .gitignore as it contains only
build artifacts. ISO and floppy image files are also gitignored.
2026-02-23 07:09:04 +01:00
34e24c9979 Add PROMPT.md with original AI prompt (AI)
Document the prompt given to the AI to implement ClaudeOS, making the
request auditable as described in the README task list requirements.
2026-02-23 07:08:34 +01:00
21 changed files with 488 additions and 4 deletions

42
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,42 @@
# ClaudeOS build environment
#
# Provides everything needed to build and test a bare-metal i386 kernel:
# - Clang + LLD for cross-compilation to i386-elf
# - GRUB 2 tools for generating bootable ISO and floppy images
# - QEMU (i386) for running integration tests
# - CMake + Ninja for the build system
# - xorriso and mtools used internally by grub-mkrescue / grub-mkimage
FROM --platform=linux/amd64 alpine:3.23
RUN apk add --no-cache \
# Core build tools
clang \
lld \
cmake \
ninja \
make \
git \
# GRUB image generation
grub \
grub-bios \
xorriso \
mtools \
# QEMU for running the OS
qemu-system-i386 \
# Handy utilities
file \
ca-certificates
# Verify the cross-compilation toolchain can produce an i386-elf binary.
# Compile and link separately so ld.lld is invoked directly, avoiding the
# host gcc linker driver (which rejects -m32 on non-x86 hosts such as aarch64).
RUN echo 'void _start(void){for(;;)__asm__("hlt");}' > /tmp/test.c \
&& clang -v -target i386-elf -march=i386 -ffreestanding -fno-stack-protector \
-c /tmp/test.c -o /tmp/test.o \
&& ld.lld -m elf_i386 -e _start --nostdlib /tmp/test.o -o /tmp/test.elf \
&& file /tmp/test.elf | grep -q "ELF 32-bit.*386" \
&& echo "Toolchain OK" \
&& rm /tmp/test.c /tmp/test.o /tmp/test.elf
WORKDIR /workspaces/claude-os

View File

@@ -0,0 +1,24 @@
{
"name": "ClaudeOS Dev",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.cmake-tools",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack"
],
"settings": {
"cmake.generator": "Ninja",
"cmake.configureSettings": {
"CMAKE_TOOLCHAIN_FILE": "${workspaceFolder}/cmake/toolchain-i386-clang.cmake"
}
}
}
},
"postCreateCommand": "echo 'ClaudeOS devcontainer ready!'",
"remoteUser": "root"
}

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build/
*.iso
*.img

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

@@ -3,12 +3,14 @@ This document describes which files are written fully by humans, which had human
## Fully Human
The following files were written completely by humans:
- HUMAN.md
- README.md
- `HUMAN.md` (except for the checkmarks in the tasklist)
- `README.md`
- Everything in `vendor/`. While we don't actually know if it was written by humans, certainly the AI used for this project has not touched it.
## Partially Human
The following files were written partially by humans:
The following files were written partially by humans or was generated by an AI and was later edited:
- `.devcontainer/Dockerfile`
## Fully AI
Any file not in the above two categories were completely written by an AI without a human manually editing the file afterwards.

7
PROMPT.md Normal file
View File

@@ -0,0 +1,7 @@
# Prompt
The following prompt was given to GitHub Copilot (Claude Sonnet 4.6) on 23 February 2026:
> Please implement the task list in this readme
The task list referred to is the one found in README.md, which describes how to build ClaudeOS — an AI-built operating system written in C, targeting 80386 hardware, providing a UNIX-like shell environment.

View File

@@ -32,9 +32,10 @@ The source code uses the following directory structure:
# Task list
An AI implementing this operating system should implement features in this order.
After each feature, it should create a new Git commit.
After each feature, it should create a new Git commit and push the commit.
The git commit should describe what is done and why certain choices were made.
The title of the commit should always end with (AI), to indicate that it was written by an AI.
Once a task is completed, it should be checked off.
- [ ] Create directory structure
- [ ] Create initial build system

0
apps/.gitkeep Normal file
View File

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)

0
docs/.gitkeep Normal file
View File

0
libs/.gitkeep Normal file
View File

0
src/.gitkeep Normal file
View File

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;
}

0
src/it/.gitkeep Normal file
View File

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

0
vendor/.gitkeep vendored Normal file
View File