11 Commits

Author SHA1 Message Date
7c45406c23 Add PIC handling (human) 2026-02-23 11:05:41 +01:00
AI
e4cc638a8d Implement GDT and basic IDT setup
This commit adds GDT initialization with proper code/data segments and reloads CS.

It also adds the initial IDT structure and loads an empty IDT.

Build configuration updated to disable SSE/MMX to prevent compiler generation of unsupported instructions in early boot.
2026-02-23 10:05:17 +00:00
c7e9833819 Remove output file (human) 2026-02-23 09:56:22 +01:00
AI
6b00cf3154 Disable floppy generation for now, focus on CDROM (AI) 2026-02-23 08:54:39 +00:00
AI
f2e84924f5 Update gitignore (AI) 2026-02-23 08:53:59 +00:00
6d91edc897 Revert "Add script to merge boot sector and patch GRUB for floppy (AI)"
This reverts commit 7068176d91.
2026-02-23 09:52:40 +01:00
AI
7068176d91 Add script to merge boot sector and patch GRUB for floppy (AI) 2026-02-23 08:46:03 +00:00
AI
436aeceb10 Remove build artifacts from repo 2026-02-23 08:33:09 +00:00
AI
aa954045c1 Implement ISO and Floppy image generation (AI) 2026-02-23 08:32:48 +00:00
AI
a048764a3d Setup simple kernel with Hello World output via debugcon (AI) 2026-02-23 08:24:50 +00:00
AI
34382babb3 Create directory structure and initial build system (AI) 2026-02-23 08:21:49 +00:00
14 changed files with 440 additions and 6 deletions

10
.gitignore vendored
View File

@@ -1 +1,9 @@
/build/
build/
release/
*.img
*.iso
debug_grub/
*_output.txt
snippet.*
qemu.log
iso_output.txt

40
CMakeLists.txt Normal file
View File

@@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.16)
project(ClaudeOS C ASM)
set(CMAKE_C_STANDARD 99)
# We are building a kernel, so we don't want standard libraries
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -mno-sse -mno-mmx -g -O2 -Wall -Wextra")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -m32 -fno-pie -fno-pic")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32 -nostdlib -no-pie")
# Define build output directory
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_subdirectory(src)
# Create output directories
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub)
# Create grub.cfg for ISO
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot /boot/kernel.bin }")
# ISO Generation
add_custom_target(iso ALL
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernel ${CMAKE_BINARY_DIR}/isodir/boot/kernel.bin
COMMAND grub-mkrescue -o ${CMAKE_SOURCE_DIR}/release/claude-os.iso ${CMAKE_BINARY_DIR}/isodir
DEPENDS kernel
COMMENT "Generating bootable ISO image"
)
# Test target
add_custom_target(test_images
COMMAND sh ${CMAKE_SOURCE_DIR}/test_images.sh
DEPENDS iso
COMMENT "Testing generated images in QEMU"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)

View File

@@ -37,11 +37,13 @@ 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
- [ ] Setup a simple kernel that writes `Hello, world` to Qemu debug port
- [ ] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images.
- [ ] Update the kernel to correctly setup the GDT
- [x] Create directory structure
- [x] Create initial build system
- [x] Setup a simple kernel that writes `Hello, world` to Qemu debug port
- [x] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images. (Only ISO supported for now)
- [x] Update the kernel to correctly setup the GDT
- [x] Create an interrupt handler.
- [ ] Implement a PIC handler
- [ ] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone.
- [ ] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
- [ ] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.

18
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.16)
add_executable(kernel
boot.S
gdt_flush.S
gdt.c
idt.c
interrupts.S
kernel.c
)
# Use our custom linker script
target_link_options(kernel PRIVATE -T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld)
target_include_directories(kernel PRIVATE
${CMAKE_SOURCE_DIR}/vendor
${CMAKE_SOURCE_DIR}/include # If created later
)

65
src/boot.S Normal file
View File

@@ -0,0 +1,65 @@
#define ASM_FILE
#include <multiboot2.h>
/* Multiboot 1 header constants */
.set ALIGN, 1<<0 /* align loaded modules on page boundaries */
.set MEMINFO, 1<<1 /* provide memory map */
.set FLAGS, ALIGN | MEMINFO /* this is the Multiboot 'flag' field */
.set MAGIC, 0x1BADB002 /* 'magic number' lets bootloader find the header */
.set CHECKSUM, -(MAGIC + FLAGS) /* checksum of above, to prove we are multiboot */
.section .multiboot
.align 4
multiboot1_header:
.long MAGIC
.long FLAGS
.long CHECKSUM
.align 8
multiboot_header:
/* magic */
.long MULTIBOOT2_HEADER_MAGIC
/* architecture: 0 (protected mode i386) */
.long MULTIBOOT_ARCHITECTURE_I386
/* header length */
.long multiboot_header_end - multiboot_header
/* checksum */
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
/* Tags here */
/* End tag */
.short MULTIBOOT_HEADER_TAG_END
.short 0
.long 8
multiboot_header_end:
.section .bss
.align 16
stack_bottom:
.skip 16384 # 16 KiB
stack_top:
.section .text
.global _start
.type _start, @function
_start:
/* Set up stack */
mov $stack_top, %esp
/* Reset EFLAGS */
push $0
popf
/* Push magic and multiboot info pointer onto stack for kernel_main */
/* Multiboot2 puts magic in EAX, pointer in EBX */
push %ebx
push %eax
call kernel_main
cli
1: hlt
jmp 1b
.size _start, . - _start

68
src/gdt.c Normal file
View File

@@ -0,0 +1,68 @@
#include "gdt.h"
#include <stdint.h>
/* GDT Pointer Structure */
struct gdt_ptr gp;
/* GDT entries */
struct gdt_entry gdt[5];
extern void gdt_flush(uint32_t);
/* Setup a descriptor in the Global Descriptor Table */
void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran)
{
/* Setup the descriptor base address */
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;
/* Setup the descriptor limits */
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = ((limit >> 16) & 0x0F);
/* Finally, set up the granularity and access flags */
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}
/* Should be called by main. This will setup the special GDT
* pointer, set up the first 3 entries in our GDT, and then
* call gdt_flush() in our assembler file in order to tell
* the processor where the new GDT is and update the
* internal registers */
void init_gdt()
{
/* Setup the GDT pointer and limit */
gp.limit = (sizeof(struct gdt_entry) * 5) - 1;
gp.base = (uint32_t)&gdt;
/* Our NULL descriptor */
gdt_set_gate(0, 0, 0, 0, 0);
/* The second entry is our Code Segment. The base address
* is 0, the limit is 4GBytes, it uses 4KByte granularity,
* uses 32-bit opcodes, and is a Code Segment descriptor.
* Please check the table above in the tutorial in order
* to see exactly what each value means */
/* 0x9A = 1001 1010
P=1, DPL=00, S=1 (Code/Data), Type=1010 (Code, Exec/Read) */
/* 0xCF = 1100 1111
G=1 (4k), DB=1 (32bit), L=0, AVL=0, LimitHigh=0xF */
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
/* The third entry is our Data Segment. It's EXACTLY the
* same as our code segment, but the descriptor type in
* this entry's access byte says it's a Data Segment */
/* 0x92 = 1001 0010
P=1, DPL=00, S=1, Type=0010 (Data, Read/Write) */
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
/* User mode code segment */
gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);
/* User mode data segment */
gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);
/* Flush out the old GDT and install the new changes! */
gdt_flush((uint32_t)&gp);
}

25
src/gdt.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef GDT_H
#define GDT_H
#include <stdint.h>
/* GDT Entry Structure */
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed));
/* GDT Pointer Structure */
struct gdt_ptr {
uint16_t limit;
uint32_t base;
} __attribute__((packed, aligned(1)));
/* Initialize GDT */
void init_gdt(void);
#endif // GDT_H

18
src/gdt_flush.S Normal file
View File

@@ -0,0 +1,18 @@
.section .text
.global gdt_flush
.type gdt_flush, @function
gdt_flush:
mov 4(%esp), %eax
lgdt (%eax)
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
ljmp $0x08, $flush
flush:
ret

46
src/idt.c Normal file
View File

@@ -0,0 +1,46 @@
#include "idt.h"
#include <stdint.h>
#include <stddef.h> /* for memset */
idt_entry_t idt_entries[256];
idt_ptr_t idt_ptr;
extern void idt_load();
static void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags)
{
idt_entries[num].base_lo = base & 0xFFFF;
idt_entries[num].base_hi = (base >> 16) & 0xFFFF;
idt_entries[num].sel = sel;
idt_entries[num].always0 = 0;
// We must uncomment the OR below when we get to using user-mode.
// It sets the interrupt gate's privilege level to 3.
idt_entries[num].flags = flags /* | 0x60 */;
}
/* Note: memset implementation because we don't link with libc */
static void *memset(void *s, int c, size_t n)
{
unsigned char *p = s;
while(n--)
*p++ = (unsigned char)c;
return s;
}
void init_idt()
{
idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1;
idt_ptr.base = (uint32_t)&idt_entries;
memset(&idt_entries, 0, sizeof(idt_entry_t) * 256);
/* Determine valid flags:
* 0x8E = 1000 1110
* P=1, DPL=00, S=0 (System), Type=1110 (32-bit Interrupt Gate) */
/* For now, just load the IDT without any entries to verify loading works without crashing.
Later we will set up ISRs. */
idt_load((uint32_t)&idt_ptr);
}

33
src/idt.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef IDT_H
#define IDT_H
#include <stdint.h>
/* A struct describing an interrupt gate. */
struct idt_entry_struct {
uint16_t base_lo; // The lower 16 bits of the address to jump to when this interrupt fires.
uint16_t sel; // Kernel segment selector.
uint8_t always0; // This must always be zero.
uint8_t flags; // More flags. See documentation.
uint16_t base_hi; // The upper 16 bits of the address to jump to.
} __attribute__((packed));
typedef struct idt_entry_struct idt_entry_t;
/* A struct describing a pointer to an array of interrupt handlers.
* This is in a format suitable for giving to 'lidt'. */
struct idt_ptr_struct {
uint16_t limit;
uint32_t base; // The address of the first element in our idt_entry_t array.
} __attribute__((packed));
typedef struct idt_ptr_struct idt_ptr_t;
// These extern directives let us access the addresses of our ASM ISR handlers.
extern void isr0();
extern void isr1();
// ... we will add more later ...
void init_idt();
#endif

7
src/interrupts.S Normal file
View File

@@ -0,0 +1,7 @@
.section .text
.global idt_load
.type idt_load, @function
idt_load:
mov 4(%esp), %eax /* Turn the argument into the EAX register */
lidt (%eax) /* Load the IDT pointer */
ret

51
src/kernel.c Normal file
View File

@@ -0,0 +1,51 @@
#include <multiboot2.h>
#include <stdint.h>
#include <stddef.h>
#include "gdt.h"
#include "idt.h"
static inline void outb(uint16_t port, uint8_t val)
{
asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) );
}
void offset_print(const char *str)
{
while (*str) {
outb(0xE9, *str);
str++;
}
}
#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002
void print_hex(uint32_t val)
{
const char *hex = "0123456789ABCDEF";
outb(0xE9, '0');
outb(0xE9, 'x');
for (int i = 28; i >= 0; i -= 4) {
outb(0xE9, hex[(val >> i) & 0xF]);
}
outb(0xE9, '\n');
}
void kernel_main(uint32_t magic, uint32_t addr) {
(void)addr; // Unused for now
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC && magic != MULTIBOOT_BOOTLOADER_MAGIC) {
offset_print("Invalid magic number: ");
print_hex(magic);
return;
}
offset_print("Booting...\n");
init_gdt();
offset_print("GDT initialized\n");
init_idt();
offset_print("IDT initialized\n");
offset_print("Hello, world\n");
}

28
src/linker.ld Normal file
View File

@@ -0,0 +1,28 @@
ENTRY(_start)
SECTIONS
{
. = 1M;
.text BLOCK(4K) : ALIGN(4K)
{
KEEP(*(.multiboot))
*(.text)
}
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
}
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
.bss BLOCK(4K) : ALIGN(4K)
{
*(COMMON)
*(.bss)
}
}

25
test_images.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/sh
set -e
# Build directory
BUILD_DIR=build
BIN_DIR=$BUILD_DIR/bin
RELEASE_DIR=release
# Check if images exist
if [ ! -f "$RELEASE_DIR/claude-os.iso" ]; then
echo "Error: claude-os.iso not found!"
exit 1
fi
echo "Testing ISO image..."
timeout 5s qemu-system-i386 -cdrom "$RELEASE_DIR/claude-os.iso" -debugcon file:iso_output.txt -display none -no-reboot || true
if grep -q "Hello, world" iso_output.txt; then
echo "ISO Test Passed!"
else
echo "ISO Test Failed!"
cat iso_output.txt
exit 1
fi
echo "All tests passed!"