Master the fundamental concepts of build a mini kernel through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksYour teaching kernel exposes syscalls via int 0x80 or sysenter with a stable ABI: number in register, args in others, return in eax. User test programs call write and exit without libc.
Kernel side:
For example, SYS_WRITE might be 1, taking fd in ebx, buffer in ecx, count in edx for 32-bit teaching ABI.
The int 0x80 syscall table you're dispatching through here is the literal mechanism Linux used for two decades before sysenter and the AMD64 syscall/sysret instructions replaced it for speed, and the EAX-as-syscall-number convention is still how strace decodes every process's kernel requests. Returning a positive value for an error instead of a negative errno is a classic bug that makes user-space code think a failed syscall actually succeeded.
Before you call the implementation done, walk failure modes on purpose. Test empty structures, single-element edge cases, maximum concurrency, and errno paths that must not crash the program. OS code usually fails in production when happy-path tests pass but invariants break under contention or memory pressure.
Keep structures small and name fields after kernel counterparts when possible. That lets you read man pages and kernel source side by side while you work. Print observable events during development; remove noisy logs once tests pass reliably.
You will implement syscall entry stub, table, and two handlers (write to VGA/serial, exit to zombie path). This exercise requires refusing pointers above user memory limit with EFAULT.
Implement the kernel side of the classic Linux i386 int 0x80 system-call interface. User code puts the syscall number in eax and its arguments in ebx, ecx and edx, then traps. The kernel dispatches through a table, validates every user pointer before touching it, and returns the result, or a negative errno, in eax. Processes have separate address spaces, and exit and sched_yield switch to another process.
cLoading…
Numbers are decimal, or hex with a 0x prefix. Missing registers are 0.
| eax | Name | Behaviour | Returns |
|---|---|---|---|
| 1 | exit | Terminate. Switch to the next live process after this one in creation order (wrapping around), or halt if there is none | never returns |
| 4 | write | fd 1 or 2 only. Copy edx bytes from user address ecx | byte count |
| 20 | getpid | pid | |
| 45 | brk | Set the break if 0x0804c000 <= ebx < 0x40000000, otherwise leave it unchanged (each process starts at 0x0804c000) | current break, in hex |
| 158 | sched_yield | Switch to the next live process, if there is one | 0 |
Errors: an unknown number returns -38 (ENOSYS). A bad fd returns -9 (EBADF). A buffer that reaches 0xc0000000 (kernel space), or includes any byte the running process has not poked, returns -14 (EFAULT). Another process's memory counts as not poked.
cLoading…
The trap line shows an unknown name as ?. Written data is quoted, with \n, \", \\ and \xNN escapes for other non-printable bytes. stats counts calls by number in ascending order, including failed and unknown calls (unknown ones appear by number), or prints none. poke at or above 0xc0000000 prints poke 0xADDR: user memory ends at 0xbfffffff (8 hex digits), and poke with no process prints poke: no running process.
Input:
cLoading…
Output:
cLoading…
eax in one place, and give every handler the same (ebx, ecx, edx) → eax shape.copy_from_user: check the range against the kernel boundary without overflowing, then check each byte against the running process's memory only.Hidden tests cover EFAULT for kernel addresses, ranges that wrap past the boundary, partially mapped buffers and other processes' memory, EBADF, ENOSYS, brk queries and rejected break values, yield with one and several processes, exit handing over to the next process, and calls after everything has exited.