diff --git a/CMakeLists.txt b/CMakeLists.txt index ee202f5..e50a305 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,13 +19,20 @@ add_subdirectory(src) file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub) -# Generate CPIO initial ramdisk from apps directory. -# All files in apps/ are packed into a newc-format CPIO archive. +# Build user-mode applications as flat binaries. +set(APPS_BIN_DIR ${CMAKE_BINARY_DIR}/apps_bin) +file(MAKE_DIRECTORY ${APPS_BIN_DIR}) +add_custom_target(apps + COMMAND ${CMAKE_SOURCE_DIR}/scripts/build_apps.sh ${CMAKE_SOURCE_DIR}/apps ${APPS_BIN_DIR} + COMMENT "Building user-mode applications" +) + +# Generate CPIO initial ramdisk from built app binaries. set(INITRD_FILE ${CMAKE_BINARY_DIR}/isodir/boot/initrd.cpio) add_custom_command( OUTPUT ${INITRD_FILE} - COMMAND ${CMAKE_SOURCE_DIR}/scripts/gen_initrd.sh ${CMAKE_SOURCE_DIR}/apps ${INITRD_FILE} - DEPENDS ${CMAKE_SOURCE_DIR}/apps + COMMAND ${CMAKE_SOURCE_DIR}/scripts/gen_initrd.sh ${APPS_BIN_DIR} ${INITRD_FILE} + DEPENDS apps COMMENT "Generating CPIO initial ramdisk" ) add_custom_target(initrd DEPENDS ${INITRD_FILE}) diff --git a/apps/hello-world/hello-world.S b/apps/hello-world/hello-world.S new file mode 100644 index 0000000..587556f --- /dev/null +++ b/apps/hello-world/hello-world.S @@ -0,0 +1,28 @@ +# hello-world.S - ClaudeOS user-mode hello world application +# Assembled as flat binary, loaded at USER_CODE_START (0x08048000) +# +# Uses INT 0x80 system calls: +# SYS_WRITE(1): EAX=1, EBX=fd, ECX=buf, EDX=len +# SYS_EXIT(0): EAX=0, EBX=code + +.code32 +.section .text +.globl _start +_start: + /* SYS_WRITE(stdout, "Hello, World\n", 13) */ + movl $1, %eax /* SYS_WRITE */ + movl $1, %ebx /* fd = stdout */ + movl $msg, %ecx /* buf = absolute address of message */ + movl $13, %edx /* len = 13 */ + int $0x80 + + /* SYS_EXIT(0) */ + movl $0, %eax /* SYS_EXIT */ + movl $0, %ebx /* exit code = 0 */ + int $0x80 + + /* Safety: infinite loop (should never reach here) */ + jmp . + +msg: + .ascii "Hello, World\n" diff --git a/apps/user.ld b/apps/user.ld new file mode 100644 index 0000000..fb55722 --- /dev/null +++ b/apps/user.ld @@ -0,0 +1,25 @@ +/* Linker script for ClaudeOS user-mode flat binary. + * Applications are loaded at 0x08048000 (USER_CODE_START). + * This produces a flat binary (no ELF headers). */ + +ENTRY(_start) + +SECTIONS { + . = 0x08048000; + + .text : { + *(.text) + } + + .rodata : { + *(.rodata*) + } + + .data : { + *(.data) + } + + .bss : { + *(.bss) + } +} diff --git a/scripts/build_apps.sh b/scripts/build_apps.sh new file mode 100755 index 0000000..6a6ebee --- /dev/null +++ b/scripts/build_apps.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# Build all user-mode applications as flat binaries. +# Usage: build_apps.sh +# Each app directory in / gets compiled and its flat binary +# is placed in /. +set -e + +APPS_DIR="$1" +OUTPUT_DIR="$2" +LINKER_SCRIPT="$APPS_DIR/user.ld" + +CC="${CC:-clang}" +OBJCOPY="${OBJCOPY:-objcopy}" +CFLAGS="-ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -mno-sse -mno-mmx -O2 -Wall" +LDFLAGS="-m32 -nostdlib -no-pie -Wl,--no-dynamic-linker" + +mkdir -p "$OUTPUT_DIR" + +for app_dir in "$APPS_DIR"/*/; do + [ -d "$app_dir" ] || continue + app_name=$(basename "$app_dir") + + echo "Building app: $app_name" + + # Collect source files + OBJ_FILES="" + for src in "$app_dir"*.S "$app_dir"*.c; do + [ -f "$src" ] || continue + obj="$OUTPUT_DIR/${app_name}_$(basename "${src%.*}").o" + $CC $CFLAGS -c "$src" -o "$obj" + OBJ_FILES="$OBJ_FILES $obj" + done + + if [ -z "$OBJ_FILES" ]; then + echo " No sources found, skipping" + continue + fi + + # Link into ELF + elf="$OUTPUT_DIR/$app_name.elf" + $CC $LDFLAGS -T "$LINKER_SCRIPT" $OBJ_FILES -o "$elf" + + # Convert to flat binary (strip non-code sections) + bin="$OUTPUT_DIR/$app_name" + $OBJCOPY -O binary --only-section=.text --only-section=.rodata --only-section=.data "$elf" "$bin" + + size=$(wc -c < "$bin") + echo " Built: $bin ($size bytes)" +done diff --git a/scripts/gen_initrd.sh b/scripts/gen_initrd.sh index 8e212ec..aa21cce 100755 --- a/scripts/gen_initrd.sh +++ b/scripts/gen_initrd.sh @@ -1,14 +1,16 @@ #!/bin/sh -# Generate CPIO initial ramdisk from apps directory -# Usage: gen_initrd.sh +# Generate CPIO initial ramdisk from built application binaries. +# Usage: gen_initrd.sh +# Packs all files in into a newc-format CPIO archive. set -e -APPS_DIR="$1" +BIN_DIR="$1" OUTPUT="$2" # Ensure output directory exists mkdir -p "$(dirname "$OUTPUT")" -cd "$APPS_DIR" -find . -not -name '.' | cpio -o -H newc > "$OUTPUT" 2>/dev/null +cd "$BIN_DIR" +# Only pack actual binary files (no .o, .elf intermediates) +find . -maxdepth 1 -type f ! -name '*.o' ! -name '*.elf' | cpio -o -H newc > "$OUTPUT" 2>/dev/null echo "Generated initrd: $(wc -c < "$OUTPUT") bytes" diff --git a/src/kernel.c b/src/kernel.c index 8dcc5e3..23027f2 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -152,64 +152,31 @@ void kernel_main(uint32_t magic, uint32_t addr) { offset_print("FAILED to kmalloc\n"); } - /* - * Create a minimal test user-mode program. - * This is flat binary machine code that calls SYS_WRITE then SYS_EXIT. - * - * The program writes "Hello from Ring 3!\n" to stdout (fd=1) via INT 0x80, - * then exits with code 42. - * - * Assembly (i386): - * ; SYS_WRITE(1, msg, 19) - * mov eax, 1 ; SYS_WRITE - * mov ebx, 1 ; fd = stdout - * call next ; get EIP for position-independent addressing - * next: - * pop ecx ; ECX = address of 'next' - * add ecx, 25 ; ECX = address of message string (offset to msg) - * mov edx, 19 ; len = 19 - * int 0x80 - * ; SYS_EXIT(42) - * mov eax, 0 ; SYS_EXIT - * mov ebx, 42 ; code = 42 - * int 0x80 - * ; loop forever (shouldn't reach here) - * jmp $ - * msg: - * db "Hello from Ring 3!", 10 - */ - static const uint8_t user_program[] = { - 0xB8, 0x01, 0x00, 0x00, 0x00, /* mov eax, 1 (SYS_WRITE) */ - 0xBB, 0x01, 0x00, 0x00, 0x00, /* mov ebx, 1 (stdout) */ - 0xE8, 0x00, 0x00, 0x00, 0x00, /* call next (push EIP) */ - /* next: offset 15 */ - 0x59, /* pop ecx */ - 0x83, 0xC1, 0x19, /* add ecx, 25 (offset from 'next' to msg) */ - 0xBA, 0x13, 0x00, 0x00, 0x00, /* mov edx, 19 (length) */ - 0xCD, 0x80, /* int 0x80 */ - /* SYS_EXIT(42): offset 26 */ - 0xB8, 0x00, 0x00, 0x00, 0x00, /* mov eax, 0 (SYS_EXIT) */ - 0xBB, 0x2A, 0x00, 0x00, 0x00, /* mov ebx, 42 (exit code) */ - 0xCD, 0x80, /* int 0x80 */ - 0xEB, 0xFE, /* jmp $ (infinite loop safety) */ - /* msg: offset 40 */ - 'H','e','l','l','o',' ','f','r','o','m',' ', - 'R','i','n','g',' ','3','!','\n' - }; + /* Load hello-world from the initrd and run it as a user process */ + cpio_entry_t hello_entry; + if (cpio_find("hello-world", &hello_entry) == 0) { + offset_print("Found hello-world in initrd ("); + print_hex(hello_entry.datasize); + offset_print(" bytes)\n"); - int32_t pid = process_create("init", user_program, sizeof(user_program)); - if (pid > 0) { - offset_print("Created init process, pid="); - print_hex((uint32_t)pid); + int32_t pid = process_create("hello-world", + hello_entry.data, + hello_entry.datasize); + if (pid > 0) { + offset_print("Created hello-world process, pid="); + print_hex((uint32_t)pid); - /* Enable interrupts before entering user mode */ - asm volatile("sti"); - offset_print("Interrupts enabled\n"); + /* Enable interrupts before entering user mode */ + asm volatile("sti"); + offset_print("Interrupts enabled\n"); - /* Enter user mode - does not return */ - process_run_first(); + /* Enter user mode - does not return */ + process_run_first(); + } else { + offset_print("FAILED to create hello-world process\n"); + } } else { - offset_print("FAILED to create init process\n"); + offset_print("hello-world not found in initrd\n"); } /* Enable interrupts */