Skip to content

JeckAsChristopher/crix

Repository files navigation

Crix OS — v0.7.0

A minimal bare-metal OS for AArch64, built with pure Clang + LLD. Designed to run on Termux (Android) — no sudo, no Linux PC needed.


Source layout

crix/
├── arch/
│   └── vectors.S          AArch64 exception vector table (EL1 SPx + EL0)
├── boot/
│   ├── boot.c             UEFI bootloader → boota64.efi (PE32+ AArch64)
│   └── efi.h              Hand-rolled UEFI types (no gnu-efi dependency)
├── fs/
│   ├── fat32.c            FAT32 driver (read + write, mounted at /mnt)
│   ├── vfs.c              Virtual filesystem (ramfs + FAT32 mount points)
│   └── virtio_blk.c       VirtIO block device driver (QEMU virt)
├── include/
│   ├── dynlink.h          ELF64 dynamic linking types, AArch64 relocs, API
│   ├── elf_loader.h       ELF64 loader types and result struct
│   ├── mm.h               PMM / VMM / slab types and function declarations
│   ├── string.h           Kernel string, UART, and formatting declarations
│   └── vfs.h              VFS types, inodes, file handles, and declarations
├── lib/
│   └── string.c           String utils, memory ops, UART driver, fmt helpers
├── mm/
│   ├── pmm.c              Buddy physical memory manager (~509 MiB free)
│   ├── slab.c             Slab allocator (9 size classes: 8–2048 bytes)
│   └── vmm.c              Page tables + MMU (identity map + higher-half)
├── user/
│   ├── lib/
│   │   └── libmath.c      Shared library — 9 exported math/string functions
│   ├── cmdd.c             EL0 command daemon — primary user-space shell
│   ├── hello.c            Minimal EL0 demo (writes + exits)
│   ├── testso.c           PIE binary exercising libmath.so via dynamic linking
│   └── user.ld            User linker script (0x41000000, two PT_LOAD segs)
├── dynlink.c              Kernel-side ELF dynamic linker (so_load/activate/patch)
├── elf_loader.c           ELF64 loader: ET_EXEC + ET_DYN (PIE) support
├── kernel.c               Kernel init, scheduler, syscall dispatch (30 calls)
├── kernel.ld              Kernel linker script (kernel_main at 0x40200000)
└── Makefile

Boot sequence

OVMF (UEFI) → boota64.efi → loads KXCZ at 0x40200000 → kernel_main()
  → SCTLR / VBAR / GIC / timer init
  → PMM (buddy) → VMM (MMU) → slab → VirtIO → FAT32 → VFS
  → scheduler init → task 0 ("kernel") starts
  → shell_run() → exec /mnt/bin/cmdd  [EL0 ERET]
      → cmdd _start() → readline loop → SVC #0 for all kernel services

Syscall ABI (SVC #0)

x8 Name Args (x0–x5) Return (x0)
1 SYS_WRITE fd, buf, len bytes written
2 SYS_EXIT code
3 SYS_READ fd, buf, len char
4 SYS_GETPID pid
7 SYS_UPTIME ms since boot
9 SYS_EXEC path, pathlen 0 / -1
10 SYS_GETCWD buf, bufsz bytes written
11 SYS_CHDIR path, pathlen 0 / -1
12 SYS_LISTDIR path,plen,idx,nbuf,nsz,tp namelen / -1
13 SYS_SPAWN type (0=counter,1=finite) task_id / -1
14 SYS_KILL task_id 0 / -1
15 SYS_MKDIR path, pathlen 0 / -1
16 SYS_REMOVE path, pathlen 0 / -1
17 SYS_WRITEFILE path,plen,data,dlen 0 / -1
18 SYS_MEMINFO buf, bufsz bytes written
19 SYS_TASKS buf, bufsz bytes written
20 SYS_SYSINFO buf, bufsz bytes written
21 SYS_STAT path,plen,buf,bufsz bytes / -1
22 SYS_READFILE path,plen,buf,bufsz bytes / -1
23 SYS_HEXDUMP path,plen,buf,bufsz bytes / -1
24 SYS_REBOOT
25 SYS_POWEROFF
27 SYS_MOUNT buf, bufsz bytes written
28 SYS_PMM buf, bufsz bytes written
29 SYS_SLAB buf, bufsz bytes written
30 SYS_MEMMAP buf, bufsz bytes written

Build (Termux)

pkg update -y && pkg upgrade -y
pkg install -y clang lld llvm mtools parted qemu-system-aarch64-headless

unzip crix.zip && cd crix
make        # build everything
make run    # launch in QEMU

Expected build output:

  [OK] OVMF : /data/data/com.termux/files/usr/share/qemu/edk2-aarch64-code.fd
  [BOOT]   boota64.efi
  [AS]     arch/vectors.o
  [CC]     lib/string.o
  [CC]     kernel.o
  [CC]     elf_loader.o
  [CC]     mm/pmm.o  mm/vmm.o  mm/slab.o
  [CC]     fs/virtio_blk.o  fs/fat32.o  fs/vfs.o
  [LD]     kernel.elf
  [BIN]    kxcz
  [CMDD]   cmdd.elf
  [USER]   hello.elf
  [IMG]    crix.img

  ✓  Build complete → crix.img

Disk image layout

ESP path Contents
/EFI/BOOT/BOOTAA64.EFI UEFI bootloader
/KXCZ Kernel flat binary
/bin/cmdd Command daemon (primary shell)
/bin/hello Minimal EL0 demo
/lib/libmath.so Shared library (ET_DYN)
/bin/testso PIE test binary (ET_DYN)

Makefile targets

Target Description
make Build everything → crix.img
make run Build + launch QEMU
make run-debug Launch QEMU paused, GDB on :1234
make inspect Show symbols / section headers
make inspect-so Detailed .so / PIE relocation dump
make clean Remove all build artifacts

Memory map

Address range Region
0x40000000–0x401FFFFF UEFI / bootloader
0x40200000–0x402FFFFF Kernel image (kxcz)
0x40300000–0x5FFFFFFF PMM free pool (~509 MiB)
0x41000000–0x411FFFFF User code (cmdd text+rodata)
0x41200000–0x413FFFFF User data+BSS (cmdd RW)
0x41400000 User stack top (SP_EL0)
0x43000000–0x431FFFFF Shared library window (SO)
0xFFFFFF8040200000+ Kernel higher-half (TTBR1)

Shared object (.so) support — v0.7.0

Overview

Crix OS now includes a kernel-side ELF dynamic linker that can load position-independent shared libraries (ET_DYN) and link them into a PIE test binary at runtime. No ld.so is needed in user space — the kernel does all the work before ERET to EL0.

New source files

File Role
include/dynlink.h ELF64 structures, AArch64 reloc constants, API
dynlink.c so_load, so_activate, so_resolve_sym, so_patch_got
user/lib/libmath.c Shared math library (9 exported functions)
user/testso.c PIE test binary — exercises all libmath functions

New ESP paths

ESP path Contents
/lib/libmath.so Shared library (-fPIC -shared)
/bin/testso PIE test binary (-pie)

How it works

kernel_main / task_spawn_user("testso"):
  1. fat32_read("/lib/libmath.so") → so_load()
       Parses ET_DYN ELF, allocates phys pages, copies PT_LOAD segments,
       applies RELATIVE relocations, extracts dynsym/dynstr.
  2. fat32_read("/bin/testso")   → elf_load() (ET_DYN path)
       Rebases PIE at USER_BASE_VA, fills L3 page tables.
  3. so_patch_got(so, exec_elf, exec_pages_pa, USER_BASE_VA)
       Walks the exec's RELA.DYN + RELA.PLT; resolves each
       GLOB_DAT / JUMP_SLOT / ABS64 symbol against libmath.so's
       dynsym table; patches the GOT/PLT entries in physical memory.
  4. so_activate(so, L2_table)
       Wires the SO's L3 into the user page table at L2[24]
       (VA 0x43000000, 2 MiB window).
  5. elf_activate() → ERET to EL0 entry point.

AArch64 relocation types handled

Constant Value Action
R_AARCH64_RELATIVE 1027 *slot = base + addend
R_AARCH64_GLOB_DAT 1025 *slot = sym_va + addend
R_AARCH64_JUMP_SLOT 1026 *slot = sym_va
R_AARCH64_ABS64 257 *slot = sym_va + addend

Wiring kernel.c

task_spawn_user() in kernel.c must be updated to call the dynamic linker when executing testso. Minimal integration:

#include "include/dynlink.h"
#include "include/elf_loader.h"

// 1. Load the shared library
SharedLib so;
uint8_t *so_buf = fat32_read("/lib/libmath.so", &so_sz);
so_load(so_buf, so_sz, &so);

// 2. Load the PIE binary
uint8_t *elf_buf = fat32_read("/bin/testso", &elf_sz);
ElfLoadResult res = elf_load(elf_buf, elf_sz);

// 3. Patch GOT (resolves all JUMP_SLOT / GLOB_DAT entries)
so_patch_got(&so, (Elf64_Ehdr *)elf_buf, res.pages_pa, USER_BASE_VA);

// 4. Map the .so into the user address space
extern uint64_t *user_l2;          // L2 table for TTBR0
so_activate(&so, user_l2);

// 5. Map the executable and ERET
elf_activate(&res);

Verify on host (before running in QEMU)

make inspect-so          # dump symbols and relocations
llvm-readelf -l testso.elf | grep LOAD   # confirm ET_DYN
llvm-nm -D libmath.so | grep " T "       # 9 exported symbols
llvm-readelf -r testso.elf | grep JUMP   # PLT slots to patch

About

OS with stable init and kernel written in C

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors