Programs

Running Chromium

Source PORTING-CHROMIUM.md Synced 2026-10-07 · b38e70a

Goal chosen: run Chromium natively on XerisOS (not stream it, not XerisWeb). Reality: you don't port Chromium's code. You make XerisOS run Linux binaries, then run the Chromium Linux build as the final, most-demanding one.


The one reframe that matters#

Nobody hand-ports Chromium's ~35M lines. Chromium builds against the Linux syscall ABI + a libc. So the project is: build a Linux-compatible layer inside XerisOS. Once a nontrivial static Linux ELF runs, Chromium is "just" an enormous, threaded, JIT-using, GPU-wanting Linux binary that stresses every part of that layer. Every milestone below is a Linux-compat capability, ordered so each runs a strictly harder class of real Linux program. Chromium is the last one.

Scale, stated plainly: this is a multi-year effort for one person, and even when it runs, a pure-software-rendered Chromium wants gigabytes of RAM and will be slow. I'd still recommend the streamed-Chrome path for using Chrome soon — but this document is the native route you asked for, done right.


Where XerisOS is today (the starting line)#

  • ELF loader (core/elf.c): loads static ET_EXEC and PIE ET_DYN, x86-64, PT_LOAD segments. No PT_INTERP, no dynamic relocations, no shared libraries.
  • Syscalls (core/syscall.c): a custom ABI (~9 calls: exit, read, write, read_file, write_file, sbrk, yield, getpid, ari) via the syscall instruction. Linux numbers/convention are NOT implemented. sbrk exists (good — that's brk).
  • Memory: 2MB identity-mapped pages, no demand paging, no per-page mmap/mprotect. .bss is near its 72MB cap (~3MB headroom).
  • SMP: AP bring-up exists, but no user-space threads / futex / pthreads.
  • No libc, no dynamic linker, no /proc, no GPU driver.

Milestones (each runs a harder class of Linux binary)#

M1 — the reality (found by reading the code): bring up user mode first#

XerisOS today runs everything in ring 0 — the apps are kernel C, not user processes; elf_load and the syscall path have no callers (scaffolding); and the GDT/STAR selectors are inconsistent (the TSS sits where sysret expects user-code/data segments). So M1's real first task is standing up ring-3 user mode, in this order, each verified on QEMU serial before the next:

  1. GDT: add user code64 (0x28) + user data (0x20) segments matching STAR; keep the TSS descriptor at its own selectors; set TSS.rsp0 to the kernel stack. (Highest risk — a wrong GDT triple-faults boot.)
  2. A launcher: read the ELF from FS, elf_load into the high window [0x10000000, +N), set the user pointer window, tag the task linux.
  3. Enter ring 3 at the entry with a valid aligned user stack (iretq frame).
  4. syscall_dispatch delegates to linux_syscall_dispatch when the task is linux-tagged (no asm change needed).
  5. Backends: sysbe_write->console, sysbe_exit->return to launcher; stub mmap/set_fs/brk. Add linux_compat.c to the Makefile. Test binary ready: tools/linux-test/hello (static ELF, entry 0x10001034, makes raw write+exit_group). Done when it prints "hello from a linux binary". Everything here is un-host-testable kernel bring-up — pure build-run-iterate.

M1 (original note) — Static "hello world" (musl, static) · weeks#

The foundation. Add a Linux x86-64 syscall path: recognize Linux numbers (rax) with the Linux register convention (rdi,rsi,rdx,r10,r8,r9), routed only for Linux processes (tag the task at load). Implement the musl startup set: arch_prctl(ARCH_SET_FS) (set FS base via wrmsr), set_tid_address, brk (wrap sbrk), mmap/munmap (anonymous, from a real allocator), write/writev, readv, exit_group, rt_sigprocmask (stub), ioctl(TCGETS)→ENOTTY. Done when: a statically-linked musl printf("hello") prints on XerisOS.

M2 — Real virtual memory · months#

Chromium (and V8) need mmap with distinct page permissions and mprotect toggling (incl. W^X for the JIT). Replace 2MB identity mapping with demand paging: 4KB pages, a page-fault handler, per-mapping permissions, anonymous + file-backed mmap, mprotect, madvise. This is a core kernel rewrite and the real gate on everything after it.

M3 — Threads + futex · months#

Chromium is massively multithreaded. Implement clone(CLONE_VM|...), a real scheduler for user threads across your APs, futex (wait/wake — pthreads mutex/ condvar depend on it), set_robust_list, gettid, per-thread TLS. Done when: a pthreads test program runs.

M4 — The syscall long tail · months#

Chromium touches dozens more: epoll_create/ctl/wait, eventfd2, memfd_create, pipe2, dup3, clock_gettime/clock_nanosleep (ideally a vDSO), poll, sched_getaffinity, prctl, getrandom, sigaction/sigaltstack, sysinfo, Unix domain + TCP sockets, statx/openat, minimal /proc/self/*. Build these out driven by what real programs demand.

M5 — Filesystem + dynamic linking (or fully static) · months#

Either (a) commit to fully static Chromium builds (simplest — no ld.so), or (b) implement PT_INTERP + a dynamic linker + shared-object relocations. Provide a POSIX-ish FS view over your FAT driver (openat, read, stat, mmap a file), plus /dev/null, /dev/urandom, /proc/self/maps.

M6 — Software GPU (SwiftShader / ANGLE-SwiftShader) · months#

Chromium's compositor wants a GL context. Build SwiftShader (Chromium's software GL) for this target — itself a large C++/threads/mmap dependency (hence it comes after M2–M4) — and wire its output into your framebuffer. Run Chromium --use-gl=swiftshader --disable-gpu.

M7 — Chromium itself · the long pole#

Build Chromium (clang + GN/ninja) as a Linux target, --single-process --no-sandbox --disable-gpu --disable-dev-shm-usage, ICU/Skia/V8 bundled. Feed it GBs of RAM. Expect the first paint to be very slow. Iterate on the missing syscalls it logs until it stops crashing. This is the finish line.


Honest recommendation#

  • To use Chrome on XerisOS soon: stream a headless Chromium's pixels over your existing TLS/net stack into the framebuffer. Days, not years; real Chrome rendering; real input. (Different from this doc — say the word and I'll spec it.)
  • To own a native Chromium: the milestones above, in order. The first genuinely-achievable, verifiable step is M1. Everything else is gated on M2 (real VM) — that's the make-or-break kernel investment.
  • XerisWeb stays worthwhile regardless: it's the only browser that runs with no Linux layer, boots instantly, and fits your RAM budget.

Concrete next step I can start now#

M1, step 1: add a Linux-ABI syscall path to core/syscall.c + syscall_entry.asm (tag Linux tasks at ELF load; dispatch Linux numbers; implement arch_prctl, brk, mmap-anon, write, writev, set_tid_address, exit_group). Then build a static musl hello-world as the test binary. It needs your rebuild-and-run loop to verify (I can't boot XerisOS), and it's the honest first brick of the whole wall.

This page mirrors the file in the XerisOS repository. Changes go there. Edit on GitHub