Changelog
Reverse-chronological log of shipped work. Dates are merge/commit dates; PR numbers link to the merge that shipped each milestone. Sections under each release: Added / Changed / Fixed / Removed.
For what’s queued up next, see the roadmap; this file is the trail of how the current state got there.
0.9.0 — Kernel self-host with TCC (May 2026)
Version bump: 0.8.1 → 0.9.0. The bootable Multiboot 2 kernel ELF is
now built end-to-end with our shipped TCC — host-side via
./build-kernel-tcc.sh, in-OS via the generated
/apps/rebuild-kernel.sh (kernel-sh script driving /apps/tcc.elf
once per source). Boot banner reports build origin
(gcc-host / tcc-host / tcc-in-os). See the
kernel rebuild guide.
Added
build-kernel-tcc.shhost driver + generator for the in-OS rebuild script. Single source of truth (one file list).src/userspace/{rebuild-kernel.sh,kend.S}staged at/apps/{rebuild-kernel.sh,kend.S}on every ISO.kernel/atomic.h— TCC-only shims for__atomic_*builtins and__builtin_unreachable, usinglock-prefixed asm.mb2_header_check.c— static layout assertion guarding boot.S’s hardcodedMB2_HEADER_LEN = 48.arch/i386/boot/build_origin.c— three-way build-origin marker.- ISO staging:
/usr/include/kernel-build/(re-exposed kernel headers),/usr/lib/tcc/include/{stdint,limits}.h(Makar TCC stubs). - Opt-in test-mode phase
REBUILD-KERNELviaTEST_CMDLINE='test_mode test=rebuild-kernel'.
Changed
kmallocroundssizeup to 4-byte alignment — root cause of a long-tail heap corruption under heavy fork/exec churn (every odd- sized allocation misaligned the next remainder block; the cascade eventually poisoned the freelist).kfreerange-checksptrand validatesblk->nextlies in-heap inside the coalesce loop.fd_closeskips kfree on out-of-range or unalignede->data. Both emit a one-line diagnostic naming the caller via__builtin_return_addressif anything else ever produces a bogus pointer.boot.S/chainload.S/isr_asm.S:#ifdef __TINYC__paths hardcode forward symbol offsets and rewrite GAS.macroas cpp#define. GCC build untouched.vendor/tinycc/tccasm.c: NULL-guardasm_expr_sumso forward symbol references error cleanly instead of segfaulting TCC.syscall.c: hoistedSYS_READDIR’s nested callback to file scope (TCC rejects gcc nested functions).libc/stdlib/abort.c: drop__builtin_unreachable()under TCC.- Boot banner now distinguishes which compiler/where built the kernel:
Host-built kernel! (GCC),Self-hosted kernel! (TCC, host build), orSelf-hosted and built inside Makar! (TCC). - task.c reaper: fixed misplaced
t->script_vars = NULLthat was inside theif (t->exec_params)block (latent leak / UAF cousin of the kmalloc-alignment bug). - Shell path-style dispatch (
/abs/fooor./foo) now peeks the file’s first 4 bytes for the ELF magic instead of relying on a.shsuffix — bash-flavoured auto-detect: ELF → exec, anything else → script interpreter.
Open (handed off)
- In-OS rebuild script runs end-to-end with correct PASS/FAIL marker
discipline, but ~19/75 per-file compiles still fail with
(null):3811692: invalid number syntax— TCC’s own BufferedFile state corruption insidetcc.elf, not a kernel exec/argv bug. Tracked separately; host-side TCC build is unaffected.
0.8.x — In-OS TCC milestone (May 2026)
Version bump: 0.7.x → 0.8.x. tcc.elf (TinyCC v0.9.27 cross-built
against the Makar libc shim) ships on every ISO at /apps/tcc.elf.
The sysroot (/usr/{include,lib} plus /usr/lib/tcc/) is auto-staged
by build-tcc.sh.
Added
/apps/tcc.elfself-rebuildscalc.elf,sh.elf,makbox.elf,hello.elfin-OS (verified bytest_tcc_rebuild_*).SYSCALL_FILE_MAXlifted to 16 MiB; kernel heap to 32 MiB to absorb TCC’s working set.- Ring-3 page faults + GPFs now deliver SIGSEGV via
task_exitrather than panicking the kernel; panic screen names the running task on ring-0 faults. - Real VFS mount table (
s_mounts[]invfs.c);root=/dev/hdaNMultiboot 2 cmdline; auto-detect ext2 → FAT32 → CD-ROM emergency./dev,/proc,/tmp,/log,/mnt/cdrom,/mnt/<name>all first-class table entries. Legacy/hd//mnt/hdaliases retired. /apps/sh.elfring-3 shell MVP with inline editing, history, variables,$?expansion, fork+execve+wait4 dispatch. Coexists with the in-kernel shell.
Fixed
- Upstream TCC NULL-deref in
vendor/tinycc/tccelf.c:fill_local_got_entriestriggered by every Makar app today (no GOT relocations). - USER_STACK_PAGES = 8 (from 1) — sh.c’s recursive-descent parser was overflowing the single 4 KiB user stack page.
0.7.0 — POSIX process model (PR #166)
Version bump: 0.6.0 → 0.7.0. fork+execve+wait4 is a significant capability layer — the rest of the userland roadmap (musl, dash, pipes, userland shell) now has the kernel-side foundation it needs.
Added — fork() + execve() + wait4() (slices 15-16)
POSIX process model on Makar. Detailed implementation walkthrough in internals §11; overview here.
- PMM frame refcounts (slice 15a).
uint8_t refcount[PMM_MAX_FRAMES]parallel to the bitmap.pmm_alloc_framesets refcount=1;pmm_free_framedecrements and only releases the bitmap bit when the count hits zero; newpmm_inc_ref/pmm_ref_count. All pre-existing single-owner callers (heap, vmm) keep their behaviour for free. vmm_clone_pd_cow()(slice 15b) — walks parent PD, marks user PTEs RO + software COW bit (VMM_PTE_COW, PTE bit 9),pmm_inc_refeach frame, mirrors into the child PD. Reloads CR3 if the parent is active.- COW
#PFhandler (slice 15c) inarch/i386/debug/debug.c— write fault on COW-tagged user PTE: refcount==1 fast path (clear COW + set RW), else alloc fresh frame + memcpy 4 KiB + dec old refcount + install fresh frame as RW. Falls through to existing panic for real faults.CR0.WPenabled atpaging_initso kernel writes also fault on RO user pages. SYS_FORK(= 2) +fork_child_iret(slice 15d).task_fork()inarch/i386/proc/task.cclones a task slot, deep-copies the fd_table via newfd_table_clone, inherits cwd/tty/user_brk, clones the PD via 15b, resets sig handlers. Child’s kernel stack hand-built so its firsttask_switchret lands infork_child_iret(intask_asm.S, a 1:1 mirror of theisr_common_stubepilogue) and iret’s back to ring 3 at exactly the same EIP where the parent’sint 0x80returns, with EAX=0.- Multi-page COW + exit-code coverage (slice 15e).
forktest.elfexercises five independent BSS sentinels (one single-page + four 4 KiB-aligned), child writes all of them after fork (independent COW faults), parent verifies all five remain at original values; child exits with status=42. SYS_EXECVE(= 11) (slice 16a) — replaces caller’s address space with a new ELF.elf_execextended to free the OLD user PD on success. Syscall handler copies path + argv into kernel scratch before the PD swap. POSIX-compliant signal handler reset on execve.execvetest.elfdoes fork → child-execvehello.elf→ parent-survives.SYS_WAIT4(= 114) +TASK_ZOMBIE(slice 16b) — new lifecycle state between RUNNING and DEAD; child holds its slot +exit_statusfor the parent to read.task_t.parent_pid+task_t.exit_statusadded.task_exitdecides ZOMBIE vs DEAD based on whether parent is a live ring-3 task (kernel-internal tasks go straight to DEAD so kernel shell children don’t pile up unwait4’d zombies). Orphan zombies auto-reaped by parent’stask_exit.forktest.elfandexecvetest.elfmigrated off busy-yielding to usesys_wait4.
Fixed
SYS_WRITEon fd 2 double-printed to COM1.t_putcharalready mirrors to serial wheng_serial_verboseis on (the default since PR #130); the explicitSerial_WriteCharloop in theFD_KIND_VGA_SERIALcase was duplicate work. Result: every chunk written byhello.elf,forktest.elf, etc. showed up twice in the serial log (“Hello, Hello, testertester!\n!\n” instead of “Hello, tester!\n”). Now only echoes explicitly when verbose is off.
Changed
- Slice queue renumbered (
CLAUDE.md+docs/roadmap.md). The old numbering had duplicate slice 13s (UTF-8 terminal and execve+wait4) and slice 14s (Per-task FD table and Userland shell), plus a “5b” hybrid that nothing else used. Queue now runs 1..21 cleanly, sub-letters reserved for tightly-grouped commit groups (15a-e for fork+COW, 16a-b for execve+wait4, 20a-f for the staged userland shell). demo-scriptdriver timeout bumped 12 s → 25 s. The script legitimately takes ~15 s of sleeps + work; the old budget was tripping a cascade into the bughunt scenarios whose windows would then absorb demo’s leftover output and start on the wrong VT.
Added — signals + preemption hardening (PR #154, slices 8 + 9)
- Per-task signal subsystem (
src/kernel/include/kernel/signal.h,src/kernel/arch/i386/proc/signal.c). Linux i386 signal numbers,sig_handler_ttypedef,SIG_DFL/SIG_IGNsentinels. Per-task handler table held in a static array indexed by task-pool slot. Default-action classifier follows Linux defaults; SIGKILL bypasses mask + handler; SIGKILL/SIGSTOP can’t have their disposition overridden. Delivery hooks atschedule()(for default-terminate and SIG_IGN) and at the return-to-ring-3 path inisr_handler/irq_handler/syscall_dispatch(for user-installed handlers via a sigframe trampoline on the user stack). - Syscalls:
SYS_KILL(37)—kill(pid, signo). Linux i386 number.SYS_SIGNAL(48)—signal(signo, handler). Returns the previous handler.SYS_SIGRETURN(119)— sigframe restore; invoked indirectly by the trampoline embedded at the bottom of every sigframe.SYS_FCNTL(55)—F_GETFL/F_SETFLwithO_NONBLOCKon stdin so non-blocking reads return-EAGAINinstead of blocking viashell_readline.
task_t.unkillableflag: idle (pid 1) and shell tasks set this, andsig_deliverdrops SIGKILL / default-terminate signals on unkillable targets instead of marking them DEAD. Without it, maktop’s F9-picker / a straykillfrom the shell could leave a VT permanently dead.- Per-task
kticksPIT-tick accounting, incremented bytimer_callbackand rendered in/proc/tasks(slice 9 phase 2). - Runtime-tunable scheduling quantum —
g_sched_quantum(default 4 PIT ticks = 40 ms slice) writable via the newsched_quantumshell builtin in[1..100](slice 9 phase 3). maktop.elf— htop-style task viewer. Resolution-agnostic viasys_term_cols/sys_term_rows. Linux-style /proc/meminfo + /proc/tasks parsing, horizontal CPU% + memory meter bars (green → yellow → red), tmux-style preserved status bar at the bottom row. Auto-refreshes every 1 s; ↑↓ navigate, F9 opens a left-side signal picker (↑↓ pick a name, Enter sends),sis a numeric-entry alternate,rforces refresh, F10 /qquits. Stays POSIX-aligned: stdin set toO_NONBLOCKviafcntl,readreturns-EAGAINwhen the keyboard ring is empty.sigtest.elf— ring-3 verifier forsys_signal+ sigframe + sigreturn. Installs SIGUSR1 handler, self-sends, asserts the handler ran./proc/meminfoLinux-style fields —MemTotal,MemFree,MemAvailable,MemUsed, plus existing heap stats.pmm_initcaches the bootloader-managed frame count as the source ofMemTotal.task_t.fb_touched— set bySYS_PUTCH_AT/SYS_TTY_CLEARon the calling task.shell_exec_elfchecks it after the child dies and only callsshell_clear_screen()for fullscreen apps, so line-mode children (cat, hello, makbox-fallback on typos) don’t have their output wiped from the screen.in_schedulere-entrancy guard inschedule(), paired withirq_save_disable/irq_restorearound the critical section. Cleared beforetask_switchso fresh tasks (whose first execution bypasses the schedule epilogue) don’t leave the flag set forever (slice 9 phase 1).- ktest:
test_signal(31 asserts) — default-action classifier, sig_send/sig_deliver, SIGKILL override of SIG_IGN, sig_send_pid, sig_get_handler round-trip, scheduler-driven default-terminate on a spawned victim.test_preempt— busy-loop victim + verify itskticksadvanced (proves timer-driven preemption).
- In-guest tests (host-driven harness, since removed):
ctrlc-kills-child— exec calc, send Ctrl+C, verify shell responsive (proves SIGINT default-terminate end-to-end).user-sigusr1-handler— runssigtest.elf; greps for the handler’s serial line, end-to-end ring-3 trampoline coverage.no-dead-in-proctasks— proves the new procfs DEAD filter.typo-doesnt-clear— proves line-mode output survives the shell’s post-exec cleanup (fb_touchedgate).
- Test-runner sync primitives (host-driven harness, since removed) — `wait_for_serial
[timeout]` and an `it_until` helper. The shell emits `[shell:ready vt=N]` to serial on every `shell_readline` entry (gated by `g_serial_verbose`), so tests can sync on "the prompt is ready" instead of `sleep N` — pexpect / Tcl-Expect style. Eliminated the typo-doesnt-clear flake.
Changed (signals + preemption)
Ctrl+Cno longer sets akb_sigintglobal; the keyboard ISR now callssig_send(focused_task, SIGINT)directly. Shell tasks installSIG_IGNfor SIGINT at startup so they survive Ctrl+C at their own prompt; the\x03byte still arrives via the keyboard ring andshell_readlinehandles it cooperatively (echo^C, drop the line). Legacykeyboard_sigint_consume()shim removed.shell_exec_elfno longer pollskeyboard_sigint_consume()to force-kill children. Ctrl+C delivers SIGINT to the focused child; the kernel terminates it viasig_deliver; the shell just yields untilt->state == TASK_DEAD.- Exec’d tasks now get a real name (basename of the path, minus a
trailing
.elf) instead of the hard-coded"exec", via the newtask_t.name_buf[16]durable-storage field. Visible in/proc/tasks,cat /proc/tasks, and maktop. - CI
run.shouter timeout for the GDB ISO test bumped 120 s → 300 s; on FAIL,tests/groups/ktest_bg.pyprints the lastktest_bg_completed/totalit observed so triage doesn’t need the artifact tarball for the basic “which suite stalled” question (separate commit, same PR).
Removed (signals + preemption)
kb_sigintstatic +keyboard_sigint_consume()function (replaced by per-task signal delivery).- The 4-byte
SCHED_QUANTUMcompile-time#define(now theg_sched_quantumruntime variable).
Added
- makbox — Makar busybox-style multicall binary
(
src/userspace/makbox.c). Applets:ls,cat,cp,mv,rm,rmdir,echo,pwd. Shell dispatch falls back to `makbox.elf` after PATH lookup misses, so bare `ls` / `cat` etc. still work without symlinks (FAT32 has none). SYS_GETCWD(215) — copiestask_current()->cwdinto a user buffer. Lets makboxpwdwork without argv injection. Userspace wrapper insrc/userspace/syscall.h.task_t.exec_params— per-task heap-allocatedexec_params_tpointer set byshell_exec_elfand consumed byexec_task_entry.- Shared test-runner framework (host-driven harness, since removed). Provides
start_qemu/stop_qemu/it/assert_serial_contains/assert_serial_not_contains/run_test.PAUSE <secs>directive insend_scriptlets a single test script sleep mid-stream so child tasks (calc) are ready before the next batch of keys lands. - New
test_makbox_pwdscenario asserts a[makbox:pwd]provenance tag emitted viaSYS_WRITE_SERIAL— proves the ring-3 path ran end-to-end (not a stale builtin).
Changed
- Shell dispatch in
shell.cgains the makbox fallback after PATH lookup; first-token tab completion advertises makbox applets. ls/cat/cpshell builtins removed fromfs_cmds[]; tidiedshell_cmd_fs.ccomments throughout.- The host-driven test harness (since removed) reused one QEMU session across all tests
(Ctrl+C /
cd /reset between scenarios) — ~10× faster than the per-scenario boot model. Headless: 7/7 in ~33 s. GUI mode preserved. 30 ms per-key pacing under TCG so bursts don’t out-run the kernel’s PS/2 ring + readline pipeline. - GRUB interactive timeout dropped 5 s → 3 s (
iso.sh,generate-hdd.sh).
Fixed
- Cross-TTY
execrace that could land a child task at a garbage EIP. Previouslyshell_exec_elfstashedpath/argc/argvin file-static globals and handed them off to the child via the static address. Two shells exec’ing concurrently from different TTYs would clobber each other; one observed crash:panic(cpu 0): PAGE FAULT … CS=0x3F8 EIP=0xE30 PROT|READ|USER. Per-taskexec_paramscloses the race —exec_task_entryreads from its own task’s slot, never a shared static. Reaped on slot reuse. - Stale
g_sigintafter Ctrl+C —shell_readlinenow drains the flag when handlingKEY_CTRL_C. Previously a buffered SIGINT could leak into the nextshell_exec_elf, killing the child task immediately on its first iteration so makbox/exec invocations produced no output and the shell just reprinted^C.
Removed
- Standalone
ls.elf/echo.elf/rm.elf/mv.elf/cp.elf(now applets inmakbox.elf). src/kernel/arch/i386/shell/shell_cmd_fileops.c(rm/rmdir/mv now in makbox).
0.5.0 — 2026-05-14
#129. First tagged release.
Added
- Per-TTY
vt_buf_tbacking grids. Writes go to the grid first; the framebuffer is only painted when that TTY is focused. Background TTYs accumulate output without contention. - tmux-style status bar at the reserved bottom row.
- Synthetic
/procfilesystem withcpuinfo,meminfo,tasks,unameas read-only files generated on demand. - Glob (
*,?) and cross-FS tab completion viavfs_complete. MAKAR_VERSIONsingle-source build constant.
Changed
- Alt+F1–F4 switch repaints via deferred FB drain
(
vtty_drain_pending), out of IRQ context.
2026-05-13 — Test infrastructure
Changed
- ccache toolchain image (
makar-build:local). Warm rebuilds ~3× faster (16.9 s cold → 5.6 s warm). - Single-kernel/two-ISO emit (
makar.isointeractive,makar-test.isoCI test_mode). KERNEL_ARGS injection lands here too. - Build-once fan-out CI: 4 parallel jobs (ktest, gdb-iso, gdb-hdd, in-guest tests).
- KVM gated behind
MAKAR_USE_KVM=1, off by default (CI reproducibility).
#125.
2026-05-12 — Keyboard hardening
Added
- Typematic-repeat filter for modifier keys.
- PS/2 LED sync (
0xED <bitmap>→ controller); boot-time LED state read.
Fixed
unsigned charaudit complete across the dispatch path — no more sign-extension hazards on sentinel compares.
#127.
2026-05-12 — Keyboard rewrite
Changed
- Full PS/2 set-1 (+ 0xE0 prefix) decoder. Layered pipeline: scancode → keycode → ASCII/sentinel → router.
- IRQ-driven per-TTY SPSC rings (up to
KB_TASK_SLOTS=4), strict make/break separation, modifier state held at the decoder. - Sentinels (
0x80–0x96) for arrows, F-keys, modifier events.
Added
kbtester.elf— live ring-3 diagnostic dumping every event (scancode/keycode/sentinel/modifier) to serial.
#124.
2026-05-08 — Preemptive tasking
Added
- Preemptive 100 Hz scheduler (PIT IRQ 0 yields every
SCHED_QUANTUM=4ticks). - Per-task
task_tplumbing —pid,cwd,tty, signal bitmasks,fd_tableplaceholder. SYS_WRITE_SERIAL(211).- Humanised
uptimebuiltin.
Fixed
- Dead-task user-PD reaper (deferred free on slot reuse) closes a UAF
that occurred when a timer IRQ landed inside
vmm_free_pd.
#123.
2026-05-02 — FAT32 userspace fileops
Added
- Syscalls 208–210:
SYS_DELETE_FILE,SYS_RENAME_FILE,SYS_DELETE_DIR. - Shell builtins
rm/rmdir/mvand ring-3 appsrm.elf/mv.elf/cp.elf.
#120.
2026-05-02 — Multi-TTY
Added
- Four independent shell tasks (
shell0–shell3), Alt+F1–F4 to switch. - Pane-aware VICS editor.
lsman/man <cmd>replacehelp.
#119.
2026-05-01 — Userspace
Added
- Linux i386 ABI userspace. Ring-3 protected mode via
iret, ELF loader (elf_exec) with argc/argv, syscall surface. execshell command.
#118.
2026-05-01 — Shell polish
Added
- GRUB menu (default Makar OS, fallback to next bootable device).
- 720p VESA default (
-vga std). - Ctrl+C sigint, tab completion,
calc.elf.
#117.
2026-04-30 — Build consolidation
Changed
- All build/test/boot operations now go through
run.sh(single entrypoint). Replaces the prior tangle of docker-compose targets and manual qemu invocations.
#116.
2026-04-29 — HDD path + auto-release
Added
- Installed-HDD boot path: image generation
(
generate-hdd.sh), interactive boot, GDB test. - Auto-release workflow on
mainmerge.
2026-04-29 — VESA panes
Added
- VESA pane abstraction (
vesa_pane_t) — phase 1 of split-pane support.
#113.
2026-04-26 — Display & I/O
Added
- Panic screen with register dump + boot-screen logo.
- Readline inline editing.
- File-I/O builtins.
- Ring-3 stdin support.
execwaits for the child task before returning to the prompt.
2026-04-25 — VMM + ring-3
Added
- Per-task page directories (
vmm_create_pd,vmm_map_page,vmm_switch). - Ring-3 entry trampoline (
ring3_enter). - Comprehensive ktest suites for memory + ring-3 prereqs.
Fixed
- Cross-platform build under macOS arm64 (Docker image targeting).
#53.
2026-04-12 — Filesystem + name
Added
- FAT32 + Medli filesystem layout (
#26/#47). - Universal VFS layer with auto-mount.
↑/↓shell history.- Installer.
- Build-timestamp banner.
Changed
- Project name takes shape — shell vocabulary deliberately mirrors
Medli (
#48). - Docker image swapped for the i686-elf cross-compiler.
- Shell split into focused source files.
2026-04-11 — Multitasking + storage
Added
- Cooperative multitasking +
int 0x80syscall stub (#42). - ATA PIO IDE driver (#41).
- MBR + GPT partition probe +
mkpart(#45).
Changed
- Source tree reshuffle: kernel moves under
src/kernel/arch/i386/.
2026-04-12 — Memory + ACPI
Added
- Paging cleanup, ACPI support, on-demand ktest harness (#44).
2026-04-09 — Rebirth
Changed
- Project rename to “Untitled OS”. GitHub Actions wired up (push triggers, Copilot-assisted PR cadence #1–#17). The build acquires real CI, the kernel acquires real structure.
2025-01 — Single-commit hiatus
A drive-by “fix assembly comments.” commit while the rest of the codebase slept.
The project then sat largely dormant from 2020 through early 2026.
2020-08 — A brief return
Changed
- Editorconfig, code-style consolidation.
- Switch from GAS-style inline assembly to standalone NASM files.
Nothing shipped beyond scaffolding. Repo went quiet again after about a week.
2019-08 — First strokes
Added
- First commit; display driver newline fix; libc stubs.
- IDT, GDT, basic IRQ handling within the week.
- Serial debug interface.
The project was unnamed and the build was driven by a hand-rolled QEMU shell script. Repo went quiet after about a week.
Roadmap
What’s tracked but not shipped — see roadmap for canonical status and the issue tracker for everything else.
- Slice 8 — Linux-style signal subsystem (sigaction,
kill()). - Slice 9 — Preemption hardening (interrupt-safe
schedule()). - Slice 16 — VGA-text fallback per-TTY backing buffers.
Long term: musl libc port → dash → in-kernel TCC for write-compile-run on bare metal. See Userland libc.