No way it works...

This commit is contained in:
2025-11-18 22:27:45 +01:00
commit 78177c0a07
17 changed files with 109146 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
_o/
build/
picolibc/
picolibc-install/

115
Makefile Normal file
View File

@@ -0,0 +1,115 @@
# Makefile for kernel with picolibc
.PHONY: all clean picolibc kernel run help
# Paths
PICOLIBC_INSTALL := $(CURDIR)/picolibc-install
BUILD_DIR := build
OUT_DIR := $(BUILD_DIR)/out
SRC_DIR := src
# Check if picolibc exists
PICOLIBC_EXISTS := $(shell test -d $(PICOLIBC_INSTALL) && echo 1 || echo 0)
# Compiler and flags
CC := gcc
AS := nasm
LD := ld
CFLAGS := -m32 -march=i686 -ffreestanding -nostdlib -fno-builtin
CFLAGS += -I$(PICOLIBC_INSTALL)/include
CFLAGS += -I$(SRC_DIR)/lib
CFLAGS += -I$(SRC_DIR)
CFLAGS += -Wall -Wextra -O2
ASFLAGS := -f elf32
LDFLAGS := -m elf_i386 -nostdlib
LDFLAGS += -L$(PICOLIBC_INSTALL)/lib
LDFLAGS += -T $(SRC_DIR)/link.ld
# Get libgcc path for compiler runtime support
LIBGCC := $(shell $(CC) -m32 -print-libgcc-file-name)
# Object files
OBJS := $(BUILD_DIR)/kasm.o $(BUILD_DIR)/kc.o $(BUILD_DIR)/duktape.o $(BUILD_DIR)/syscalls.o
# Default target
all: check-picolibc kernel
# Help target
help:
@echo "Makefile for kernel with picolibc"
@echo ""
@echo "Targets:"
@echo " picolibc - Download and build picolibc"
@echo " kernel - Build the kernel (requires picolibc)"
@echo " run - Build and run the kernel in QEMU"
@echo " clean - Clean build artifacts"
@echo " clean-all - Clean everything including picolibc"
@echo " help - Show this help message"
# Build picolibc
picolibc:
@echo "Building picolibc..."
@chmod +x build-picolibc.sh
@./build-picolibc.sh
# Check if picolibc is built
check-picolibc:
ifneq ($(PICOLIBC_EXISTS),1)
@echo "Error: Picolibc not found. Building it now..."
@$(MAKE) picolibc
endif
# Create build directories
$(BUILD_DIR):
@mkdir -p $(BUILD_DIR)
$(OUT_DIR): | $(BUILD_DIR)
@mkdir -p $(OUT_DIR)
# Build boot assembly
$(BUILD_DIR)/kasm.o: $(SRC_DIR)/boot/kernel.asm | $(BUILD_DIR)
@echo "Assembling boot code..."
@$(AS) $(ASFLAGS) $< -o $@
# Build duktape
$(BUILD_DIR)/duktape.o: $(SRC_DIR)/lib/duktape.c $(SRC_DIR)/lib/duktape.h | $(BUILD_DIR)
@echo "Building Duktape..."
@$(CC) $(CFLAGS) -c $< -o $@
# Build kernel
$(BUILD_DIR)/kc.o: $(SRC_DIR)/kernel/kernel.c | $(BUILD_DIR)
@echo "Building kernel..."
@$(CC) $(CFLAGS) -c $< -o $@
# Build syscalls
$(BUILD_DIR)/syscalls.o: $(SRC_DIR)/lib/syscalls.c | $(BUILD_DIR)
@echo "Building syscalls..."
@$(CC) $(CFLAGS) -c $< -o $@
# Link kernel
$(OUT_DIR)/kernel: $(OBJS) | $(OUT_DIR)
@echo "Linking kernel..."
@$(LD) $(LDFLAGS) -o $@ $(OBJS) -lc $(LIBGCC)
@echo "Build complete: $@"
# Main kernel target
kernel: check-picolibc $(OUT_DIR)/kernel
# Run in QEMU
run: kernel
@echo "Running kernel in QEMU..."
@qemu-system-i386 -kernel $(OUT_DIR)/kernel
# Clean build artifacts
clean:
@echo "Cleaning build artifacts..."
@rm -rf $(BUILD_DIR)
# Clean everything including picolibc
clean-all: clean
@echo "Cleaning picolibc..."
@rm -rf picolibc picolibc-install
.DEFAULT_GOAL := help

56
build-picolibc.sh Normal file
View File

@@ -0,0 +1,56 @@
#!/bin/bash
# Script to build picolibc for i686 freestanding environment
set -e
PICOLIBC_DIR="picolibc"
BUILD_DIR="build/picolibc"
INSTALL_DIR="$(pwd)/picolibc-install"
echo "=== Building Picolibc for i686 freestanding ==="
# Check if picolibc exists
if [ ! -d "$PICOLIBC_DIR" ]; then
echo "Error: Picolibc source not found at $PICOLIBC_DIR"
echo "Please clone picolibc manually:"
echo " git clone https://github.com/picolibc/picolibc.git"
exit 1
fi
# Get current version
cd "$PICOLIBC_DIR"
CURRENT_VERSION=$(git describe --tags 2>/dev/null || echo "unknown")
echo "Using picolibc version: $CURRENT_VERSION"
cd ..
# Create build directory
mkdir -p "$BUILD_DIR"
echo "Configuring picolibc with meson..."
meson setup "$BUILD_DIR" "$PICOLIBC_DIR" \
--cross-file picolibc-i686.txt \
--prefix="$INSTALL_DIR" \
--wipe \
-Dmultilib=false \
-Dpicocrt=false \
-Dsemihost=false \
-Dspecsdir=none \
-Dtinystdio=true \
-Dio-long-long=true \
-Dformat-default=double \
-Dtests=false
echo "Building picolibc..."
meson compile -C "$BUILD_DIR"
echo "Installing picolibc..."
meson install -C "$BUILD_DIR"
echo ""
echo "=== Picolibc build complete! ==="
echo "Installation directory: $INSTALL_DIR"
echo ""
echo "To use picolibc in your kernel:"
echo " Include path: -I$INSTALL_DIR/include"
echo " Library path: -L$INSTALL_DIR/lib"
echo " Link with: -lc"

18
picolibc-i686.txt Normal file
View File

@@ -0,0 +1,18 @@
# Meson cross-compilation file for i686-elf with picolibc
[binaries]
c = 'gcc'
ar = 'ar'
as = 'as'
ld = 'ld'
strip = 'strip'
[host_machine]
system = 'none'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'
[properties]
c_args = ['-m32', '-march=i686']
c_link_args = ['-m32']
skip_sanity_check = true

47
quickstart.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Quick start script - builds picolibc and kernel, then runs it
set -e
echo "=========================================="
echo " Kernel with Picolibc - Quick Start"
echo "=========================================="
echo ""
# Check for required tools
echo "Checking prerequisites..."
command -v gcc >/dev/null 2>&1 || { echo "Error: gcc not found"; exit 1; }
command -v nasm >/dev/null 2>&1 || { echo "Error: nasm not found"; exit 1; }
command -v ld >/dev/null 2>&1 || { echo "Error: ld not found"; exit 1; }
command -v qemu-system-i386 >/dev/null 2>&1 || { echo "Error: qemu-system-i386 not found"; exit 1; }
if ! command -v meson >/dev/null 2>&1; then
echo "Warning: meson not found"
echo "Install with: pip install --user meson ninja"
echo "or: sudo apt install meson ninja-build"
exit 1
fi
echo "✓ All prerequisites found"
echo ""
# Build picolibc if not already built
if [ ! -d "picolibc-install" ]; then
echo "Step 1: Building picolibc (this may take a few minutes)..."
./build-picolibc.sh
else
echo "Step 1: Picolibc already built (skipping)"
fi
echo ""
echo "Step 2: Building kernel..."
make kernel
echo ""
echo "Step 3: Running kernel in QEMU..."
echo "(Press Ctrl+A then X to exit QEMU)"
echo ""
sleep 2
make run

View File

@@ -0,0 +1,59 @@
#!/bin/bash
# Build script for kernel with picolibc
set -e
# Paths
PICOLIBC_INSTALL="$(pwd)/picolibc-install"
BUILD_DIR="build"
OUT_DIR="$BUILD_DIR/out"
# Check if picolibc is installed
if [ ! -d "$PICOLIBC_INSTALL" ]; then
echo "Error: Picolibc not found at $PICOLIBC_INSTALL"
echo "Please run ./build-picolibc.sh first"
exit 1
fi
# Create build directories
mkdir -p "$BUILD_DIR"
mkdir -p "$OUT_DIR"
# Compiler flags
CFLAGS="-m32 -march=i686 -ffreestanding -nostdlib -fno-builtin"
CFLAGS="$CFLAGS -I$PICOLIBC_INSTALL/include"
CFLAGS="$CFLAGS -I./src/lib"
CFLAGS="$CFLAGS -I./src"
CFLAGS="$CFLAGS -Wall -Wextra"
LDFLAGS="-m elf_i386 -nostdlib"
LDFLAGS="$LDFLAGS -L$PICOLIBC_INSTALL/lib"
echo "=== Building kernel with picolibc ==="
# Build boot assembly
echo "Assembling boot code..."
nasm -f elf32 src/boot/kernel.asm -o "$BUILD_DIR/kasm.o"
# Build duktape with picolibc
echo "Building Duktape..."
gcc $CFLAGS -c src/lib/duktape.c -o "$BUILD_DIR/duktape.o"
# Build kernel
echo "Building kernel..."
gcc $CFLAGS -c src/kernel/kernel.c -o "$BUILD_DIR/kc.o"
# Link everything together
echo "Linking kernel..."
ld $LDFLAGS -T src/link.ld -o "$OUT_DIR/kernel" \
"$BUILD_DIR/kasm.o" \
"$BUILD_DIR/kc.o" \
"$BUILD_DIR/duktape.o" \
-lc
echo ""
echo "=== Build complete! ==="
echo "Kernel binary: $OUT_DIR/kernel"
echo ""
echo "To run the kernel:"
echo " qemu-system-i386 -kernel $OUT_DIR/kernel"

58
scripts/embed_js.py Executable file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Embed JavaScript file as a C string constant
"""
import sys
def escape_c_string(s):
"""Escape a string for use in C source code"""
result = []
for char in s:
if char == "\n":
result.append("\\n")
elif char == "\r":
result.append("\\r")
elif char == "\t":
result.append("\\t")
elif char == "\\":
result.append("\\\\")
elif char == '"':
result.append('\\"')
elif ord(char) < 32 or ord(char) > 126:
result.append(f"\\x{ord(char):02x}")
else:
result.append(char)
return "".join(result)
def main():
if len(sys.argv) != 2:
print("Usage: embed_js.py <input.js>", file=sys.stderr)
sys.exit(1)
input_file = sys.argv[1]
try:
with open(input_file, "r") as f:
js_code = f.read()
except FileNotFoundError:
print(f"Error: File not found: {input_file}", file=sys.stderr)
sys.exit(1)
# Generate C header file
print("/* Auto-generated file - do not edit */")
print("#ifndef EMBEDDED_JS_H")
print("#define EMBEDDED_JS_H")
print()
escaped = escape_c_string(js_code)
print(f'const char *embedded_js_code = "{escaped}";')
print()
print("#endif /* EMBEDDED_JS_H */")
if __name__ == "__main__":
main()

77
scripts/run.sh Executable file
View File

@@ -0,0 +1,77 @@
#!/bin/bash
# Build and run kernel with picolibc
set -e
# Paths
PICOLIBC_INSTALL="$(pwd)/picolibc-install"
BUILD_DIR="build"
OUT_DIR="$BUILD_DIR/out"
# Check if picolibc is installed
if [ ! -d "$PICOLIBC_INSTALL" ]; then
echo "Error: Picolibc not found at $PICOLIBC_INSTALL"
echo "Please run: make picolibc"
exit 1
fi
# Create build directories
mkdir -p "$BUILD_DIR"
mkdir -p "$OUT_DIR"
# Copy JavaScript source to build directory
cp src/os/index.js build/
# Generate embedded JavaScript header
echo "Generating embedded JavaScript..."
python3 scripts/embed_js.py build/index.js > "$BUILD_DIR/embedded_js.h"
# Compiler flags
CFLAGS="-m32 -march=i686 -ffreestanding -nostdlib -fno-builtin"
CFLAGS="$CFLAGS -I$PICOLIBC_INSTALL/include"
CFLAGS="$CFLAGS -I./src/lib"
CFLAGS="$CFLAGS -I./src"
CFLAGS="$CFLAGS -I$BUILD_DIR"
CFLAGS="$CFLAGS -Wall -Wextra"
LDFLAGS="-m elf_i386 -nostdlib"
LDFLAGS="$LDFLAGS -L$PICOLIBC_INSTALL/lib"
# Get libgcc path
LIBGCC=$(gcc -m32 -print-libgcc-file-name)
echo "=== Building kernel with picolibc ==="
# Build boot assembly
echo "Assembling boot code..."
nasm -f elf32 src/boot/kernel.asm -o "$BUILD_DIR/kasm.o"
# Build duktape
echo "Building Duktape..."
gcc $CFLAGS -c src/lib/duktape.c -o "$BUILD_DIR/duktape.o"
# Build kernel
echo "Building kernel..."
gcc $CFLAGS -c src/kernel/kernel.c -o "$BUILD_DIR/kc.o"
# Build syscalls
echo "Building syscalls..."
gcc $CFLAGS -c src/lib/syscalls.c -o "$BUILD_DIR/syscalls.o"
# Link everything together
echo "Linking kernel..."
ld $LDFLAGS -T src/link.ld -o "$OUT_DIR/kernel" \
"$BUILD_DIR/kasm.o" \
"$BUILD_DIR/kc.o" \
"$BUILD_DIR/duktape.o" \
"$BUILD_DIR/syscalls.o" \
-lc "$LIBGCC"
echo ""
echo "=== Build complete! ==="
echo ""
echo "Running kernel in QEMU..."
echo "(Press Ctrl+A then X to exit)"
echo ""
qemu-system-i386 -kernel "$OUT_DIR/kernel"

17
src/boot/kernel.asm Normal file
View File

@@ -0,0 +1,17 @@
bits 32 ;nasm directive
section .text
;multiboot spec
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
extern kmain ;kmain is defined in the kernel.c file
start:
cli ; stop interrupts
call kmain
hlt ; halt the CPU

89
src/kernel/kernel.c Normal file
View File

@@ -0,0 +1,89 @@
#include <string.h>
#include <stdint.h>
#include "lib/duktape.h"
#include "embedded_js.h"
#define WHITE_TXT 0x0F
// Forward declaration
unsigned int k_printf(char *message, unsigned int line);
duk_context *ctx;
duk_ret_t native_print(duk_context *ctx)
{
const char *msg = duk_to_string(ctx, 0);
// k_clear_screen();
k_printf((char *)msg, 0);
return 0;
}
duk_ret_t native_write_memory(duk_context *ctx)
{
// Get the address (first argument)
uint32_t address = (uint32_t)duk_to_uint32(ctx, 0);
// Get the value to write (second argument)
uint8_t value = (uint8_t)duk_to_uint32(ctx, 1);
// Write the value to the address
uint8_t *ptr = (uint8_t *)address;
*ptr = value;
return 0;
}
void kmain()
{
// Initialize Duktape heap
ctx = duk_create_heap_default();
// Register native print function
duk_push_c_function(ctx, native_print, DUK_VARARGS);
duk_put_global_string(ctx, "print");
// Register native memory write function
duk_push_c_function(ctx, native_write_memory, 2);
duk_put_global_string(ctx, "writeMemory");
// Execute embedded JavaScript code from build/index.js
duk_push_string(ctx, embedded_js_code);
duk_int_t returnCode = duk_peval(ctx);
if (returnCode != 0)
{
// Error occurred - display stack trace
duk_safe_to_stacktrace(ctx, -1);
k_printf((char *)duk_safe_to_string(ctx, -1), 1);
}
duk_pop(ctx);
}
unsigned int k_printf(char *message, unsigned int line)
{
char *vidmem = (char *)0xb8000;
unsigned int i = 0;
i = (line * 80 * 2);
while (*message != 0)
{
if (*message == '\n') // check for a new line
{
line++;
i = (line * 80 * 2);
*message++;
}
else
{
vidmem[i] = *message;
*message++;
i++;
vidmem[i] = WHITE_TXT;
i++;
};
};
return (1);
}

3779
src/lib/duk_config.h Normal file

File diff suppressed because it is too large Load Diff

1911
src/lib/duk_source_meta.json Normal file

File diff suppressed because it is too large Load Diff

101351
src/lib/duktape.c Normal file

File diff suppressed because it is too large Load Diff

1456
src/lib/duktape.h Normal file

File diff suppressed because it is too large Load Diff

33
src/lib/syscalls.c Normal file
View File

@@ -0,0 +1,33 @@
/*
* Minimal system stubs for freestanding picolibc
*/
#include <sys/types.h>
#include <sys/time.h>
#include <errno.h>
/* Exit function - halt the system */
void _exit(int status)
{
(void)status;
/* Halt the CPU */
while (1)
{
__asm__ volatile("hlt");
}
}
/* Get time of day - not supported in freestanding */
int gettimeofday(struct timeval *tv, void *tz)
{
(void)tz;
if (tv)
{
tv->tv_sec = 0;
tv->tv_usec = 0;
}
return 0;
}
/* Sbrk - memory allocation (picolibc uses __heap_start and __heap_end from linker script) */
/* No need to implement sbrk as picolibc uses the linker symbols directly */

40
src/link.ld Normal file
View File

@@ -0,0 +1,40 @@
/*
* link.ld
*/
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x100000;
.text : {
*(.text)
*(.text.*)
*(.rodata)
*(.rodata.*)
}
.data : {
*(.data)
*(.data.*)
}
.bss : {
*(.bss)
*(.bss.*)
*(COMMON)
}
/* Heap for malloc */
. = ALIGN(4096);
__heap_start = .;
. = . + 0x100000; /* 1MB heap */
__heap_end = .;
/* Stack grows downward from high memory */
. = ALIGN(4096);
__stack_bottom = .;
. = . + 0x10000; /* 64KB stack */
__stack_top = .;
}

36
src/os/index.js Normal file
View File

@@ -0,0 +1,36 @@
clearScreen();
print("Hello World!");
// writeMemory(0xb8000, 0x48);
// writeMemory(0xb8001, 0x0f);
// writeMemory(0xb8002, 0x65);
// writeMemory(0xb8003, 0x0f);
// writeMemory(0xb8004, 0x6c);
// writeMemory(0xb8005, 0x0f);
// writeMemory(0xb8006, 0x6c);
// writeMemory(0xb8007, 0x0f);
// writeMemory(0xb8008, 0x6f);
// writeMemory(0xb8009, 0x0f);
function clearScreen() {
var i = 0;
while (i < 80 * 25 * 2) {
writeMemory(0xb8000 + i, 0x00);
i++;
}
}
function printChar(char, offset) {
writeMemory(0xb8000 + offset * 2, char.charCodeAt(0));
writeMemory(0xb8000 + offset * 2 + 1, 0x0f); // White on black
}
function print(str) {
for (var i = 0; i < str.length; i++) {
printChar(str.charAt(i), i);
}
}