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:
@@ -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})
|
||||
|
||||
28
apps/hello-world/hello-world.S
Normal file
28
apps/hello-world/hello-world.S
Normal 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
25
apps/user.ld
Normal 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
49
scripts/build_apps.sh
Executable 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
|
||||
@@ -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"
|
||||
|
||||
75
src/kernel.c
75
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 */
|
||||
|
||||
Reference in New Issue
Block a user