There was an attempt... (AI)
This commit is contained in:
9
CMakeLists.txt
Normal file
9
CMakeLists.txt
Normal 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)
|
||||
59
cmake/toolchain-i386-clang.cmake
Normal file
59
cmake/toolchain-i386-clang.cmake
Normal 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
33
src/CMakeLists.txt
Normal 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
92
src/boot/boot.s
Normal 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
39
src/debugport.c
Normal 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
34
src/include/debugport.h
Normal 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
41
src/include/io.h
Normal 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
45
src/kernel.c
Normal 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
53
src/linker.ld
Normal 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 = .;
|
||||
}
|
||||
Reference in New Issue
Block a user