feat: hello-world user-mode app loaded from initrd via VFS

- Created apps/hello-world/hello-world.S: AT&T assembly user program that
  calls SYS_WRITE to print 'Hello, World' then SYS_EXIT(0)
- Created apps/user.ld: linker script for user apps at 0x08048000
- Created scripts/build_apps.sh: builds each app dir into flat binary
- Updated CMakeLists.txt: added apps build target in ISO pipeline
- Updated gen_initrd.sh: packs built binaries from build/apps_bin/
- Updated kernel.c: replaced inline machine code with VFS-based loading
  of hello-world from /initrd/hello-world via cpio_find()

Verified: hello-world binary (49 bytes) loads from CPIO initrd,
prints 'Hello, World', and exits with code 0.
This commit is contained in:
AI
2026-02-23 12:30:36 +00:00
parent 0c5aa72fd3
commit a6e6e3d8ca
6 changed files with 141 additions and 63 deletions

View File

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

View File

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

25
apps/user.ld Normal file
View File

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

49
scripts/build_apps.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/sh
# Build all user-mode applications as flat binaries.
# Usage: build_apps.sh <apps_dir> <output_dir>
# Each app directory in <apps_dir>/ gets compiled and its flat binary
# is placed in <output_dir>/.
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

View File

@@ -1,14 +1,16 @@
#!/bin/sh
# Generate CPIO initial ramdisk from apps directory
# Usage: gen_initrd.sh <apps_dir> <output_file>
# Generate CPIO initial ramdisk from built application binaries.
# Usage: gen_initrd.sh <binaries_dir> <output_file>
# Packs all files in <binaries_dir> 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"

View File

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